From 1fa3d66e6276755746e0b6e42fb0058ca4de963f Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 24 Mar 2022 14:50:04 +0100 Subject: [PATCH] Merge commit 'd0cf3481a84e3aa68c2f185c460e282af36ebc42' into clippyup --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 3 + .../src/casts/cast_enum_constructor.rs | 21 + clippy_lints/src/casts/mod.rs | 21 + clippy_lints/src/collapsible_if.rs | 4 +- clippy_lints/src/doc.rs | 15 +- clippy_lints/src/lib.register_all.rs | 4 +- clippy_lints/src/lib.register_complexity.rs | 1 + clippy_lints/src/lib.register_lints.rs | 3 + clippy_lints/src/lib.register_nursery.rs | 1 + clippy_lints/src/lib.register_pedantic.rs | 1 + clippy_lints/src/lib.register_perf.rs | 1 - clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.register_style.rs | 1 - clippy_lints/src/lib.register_suspicious.rs | 1 + clippy_lints/src/lib.rs | 1 + clippy_lints/src/matches/match_same_arms.rs | 392 ++++++++++++++++-- clippy_lints/src/methods/map_flatten.rs | 128 +++--- clippy_lints/src/methods/mod.rs | 81 +++- clippy_lints/src/methods/or_then_unwrap.rs | 68 +++ clippy_lints/src/methods/unnecessary_join.rs | 41 ++ .../src/methods/unnecessary_lazy_eval.rs | 29 +- .../src/methods/unnecessary_to_owned.rs | 46 +- clippy_lints/src/ptr.rs | 2 +- .../src/single_component_path_imports.rs | 18 +- clippy_lints/src/transmute/mod.rs | 3 +- .../src/transmute/transmute_undefined_repr.rs | 98 +++-- clippy_lints/src/try_err.rs | 2 +- .../internal_lints/metadata_collector.rs | 3 +- clippy_lints/src/write.rs | 9 +- clippy_utils/src/diagnostics.rs | 86 +++- clippy_utils/src/sugg.rs | 2 +- rust-toolchain | 2 +- tests/ui/cast_enum_constructor.rs | 17 + tests/ui/cast_enum_constructor.stderr | 16 + tests/ui/map_flatten.rs | 76 ++-- tests/ui/map_flatten.stderr | 129 ++++-- ...latten.fixed => map_flatten_fixable.fixed} | 0 tests/ui/map_flatten_fixable.rs | 31 ++ tests/ui/map_flatten_fixable.stderr | 80 ++++ tests/ui/match_same_arms.stderr | 165 ++++---- tests/ui/match_same_arms2.rs | 53 +++ tests/ui/match_same_arms2.stderr | 248 +++++------ tests/ui/or_then_unwrap.fixed | 52 +++ tests/ui/or_then_unwrap.rs | 52 +++ tests/ui/or_then_unwrap.stderr | 22 + tests/ui/ptr_arg.rs | 7 + .../single_component_path_imports_macro.fixed | 20 - .../ui/single_component_path_imports_macro.rs | 4 +- ...single_component_path_imports_macro.stderr | 10 - tests/ui/transmute_undefined_repr.rs | 55 ++- tests/ui/transmute_undefined_repr.stderr | 34 +- ...transmutes_expressible_as_ptr_casts.stderr | 4 +- tests/ui/unnecessary_join.fixed | 35 ++ tests/ui/unnecessary_join.rs | 37 ++ tests/ui/unnecessary_join.stderr | 20 + tests/ui/unnecessary_lazy_eval.fixed | 8 + tests/ui/unnecessary_lazy_eval.rs | 8 + tests/ui/unnecessary_lazy_eval.stderr | 145 +++++-- .../ui/unnecessary_lazy_eval_unfixable.stderr | 12 +- tests/ui/unnecessary_to_owned.fixed | 48 +++ tests/ui/unnecessary_to_owned.rs | 48 +++ tests/ui/unnecessary_to_owned.stderr | 8 +- 63 files changed, 1973 insertions(+), 562 deletions(-) create mode 100644 clippy_lints/src/casts/cast_enum_constructor.rs create mode 100644 clippy_lints/src/methods/or_then_unwrap.rs create mode 100644 clippy_lints/src/methods/unnecessary_join.rs create mode 100644 tests/ui/cast_enum_constructor.rs create mode 100644 tests/ui/cast_enum_constructor.stderr rename tests/ui/{map_flatten.fixed => map_flatten_fixable.fixed} (100%) create mode 100644 tests/ui/map_flatten_fixable.rs create mode 100644 tests/ui/map_flatten_fixable.stderr create mode 100644 tests/ui/or_then_unwrap.fixed create mode 100644 tests/ui/or_then_unwrap.rs create mode 100644 tests/ui/or_then_unwrap.stderr delete mode 100644 tests/ui/single_component_path_imports_macro.fixed delete mode 100644 tests/ui/single_component_path_imports_macro.stderr create mode 100644 tests/ui/unnecessary_join.fixed create mode 100644 tests/ui/unnecessary_join.rs create mode 100644 tests/ui/unnecessary_join.stderr diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 68877efc9..b6f70a7f1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -18,7 +18,7 @@ body: id: reproducer attributes: label: Reproducer - description: Please provide the code and steps to repoduce the bug + description: Please provide the code and steps to reproduce the bug value: | I tried this code: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bc393d60..88f71931d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3069,6 +3069,7 @@ Released 2018-09-13 [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata [`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons +[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor [`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation [`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless [`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation @@ -3366,6 +3367,7 @@ Released 2018-09-13 [`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 [`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call +[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap [`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 [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic @@ -3506,6 +3508,7 @@ Released 2018-09-13 [`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map [`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map [`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold +[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`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 diff --git a/clippy_lints/src/casts/cast_enum_constructor.rs b/clippy_lints/src/casts/cast_enum_constructor.rs new file mode 100644 index 000000000..1973692e1 --- /dev/null +++ b/clippy_lints/src/casts/cast_enum_constructor.rs @@ -0,0 +1,21 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; + +use super::CAST_ENUM_CONSTRUCTOR; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>) { + if matches!(cast_from.kind(), ty::FnDef(..)) + && let ExprKind::Path(path) = &cast_expr.kind + && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = cx.qpath_res(path, cast_expr.hir_id) + { + span_lint( + cx, + CAST_ENUM_CONSTRUCTOR, + expr.span, + "cast of an enum tuple constructor to an integer", + ); + } +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 6a0eabd08..be59145af 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -1,3 +1,4 @@ +mod cast_enum_constructor; mod cast_lossless; mod cast_possible_truncation; mod cast_possible_wrap; @@ -454,6 +455,24 @@ declare_clippy_lint! { "casting using `as` between raw pointers to slices of types with different sizes" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts from an enum tuple constructor to an integer. + /// + /// ### Why is this bad? + /// The cast is easily confused with casting a c-like enum value to an integer. + /// + /// ### Example + /// ```rust + /// enum E { X(i32) }; + /// let _ = E::X as usize; + /// ``` + #[clippy::version = "1.61.0"] + pub CAST_ENUM_CONSTRUCTOR, + suspicious, + "casts from an enum tuple constructor to an integer" +} + pub struct Casts { msrv: Option, } @@ -481,6 +500,7 @@ impl_lint_pass!(Casts => [ CHAR_LIT_AS_U8, PTR_AS_PTR, CAST_ENUM_TRUNCATION, + CAST_ENUM_CONSTRUCTOR ]); impl<'tcx> LateLintPass<'tcx> for Casts { @@ -518,6 +538,7 @@ impl<'tcx> LateLintPass<'tcx> for Casts { cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to); } cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv); + cast_enum_constructor::check(cx, expr, cast_expr, cast_from); } } diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index f03f34e5a..eae2723a7 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -42,7 +42,7 @@ declare_clippy_lint! { /// /// Should be written: /// - /// ```rust.ignore + /// ```rust,ignore /// if x && y { /// … /// } @@ -76,7 +76,7 @@ declare_clippy_lint! { /// /// Should be written: /// - /// ```rust.ignore + /// ```rust,ignore /// if x { /// … /// } else if y { diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 16173580f..703aa458f 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -637,12 +637,6 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { loop { match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => match &item.kind { - // Tests with one of these items are ignored - ItemKind::Static(..) - | ItemKind::Const(..) - | ItemKind::ExternCrate(..) - | ItemKind::ForeignMod(..) => return false, - // We found a main function ... ItemKind::Fn(box Fn { sig, body: Some(block), .. }) if item.ident.name == sym::main => { @@ -661,8 +655,13 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) { return false; } }, - // Another function was found; this case is ignored too - ItemKind::Fn(..) => return false, + // Tests with one of these items are ignored + ItemKind::Static(..) + | ItemKind::Const(..) + | ItemKind::ExternCrate(..) + | ItemKind::ForeignMod(..) + // Another function was found; this case is ignored + | ItemKind::Fn(..) => return false, _ => {}, }, Ok(None) => break, diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 23bca5a0e..132a46626 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -23,6 +23,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), LintId::of(booleans::LOGIC_BUG), LintId::of(booleans::NONMINIMAL_BOOL), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(casts::CAST_REF_TO_MUT), LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES), @@ -166,7 +167,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::ITER_NTH_ZERO), LintId::of(methods::ITER_OVEREAGER_CLONED), LintId::of(methods::ITER_SKIP_NEXT), - LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_FILTER_MAP), LintId::of(methods::MANUAL_FIND_MAP), LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), @@ -182,6 +182,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::OPTION_FILTER_MAP), LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::OR_FUN_CALL), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SHOULD_IMPLEMENT_TRAIT), @@ -290,7 +291,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE), LintId::of(transmute::WRONG_TRANSMUTE), LintId::of(transmuting_null::TRANSMUTING_NULL), - LintId::of(try_err::TRY_ERR), LintId::of(types::BORROWED_BOX), LintId::of(types::BOX_COLLECTION), LintId::of(types::REDUNDANT_ALLOCATION), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index 68d6c6ce5..a2ce69065 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -47,6 +47,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), + LintId::of(methods::OR_THEN_UNWRAP), LintId::of(methods::SEARCH_IS_SOME), LintId::of(methods::SKIP_WHILE_NEXT), LintId::of(methods::UNNECESSARY_FILTER_MAP), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 1a45763a8..21f1ef562 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -70,6 +70,7 @@ store.register_lints(&[ cargo::REDUNDANT_FEATURE_NAMES, cargo::WILDCARD_DEPENDENCIES, case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + casts::CAST_ENUM_CONSTRUCTOR, casts::CAST_ENUM_TRUNCATION, casts::CAST_LOSSLESS, casts::CAST_POSSIBLE_TRUNCATION, @@ -319,6 +320,7 @@ store.register_lints(&[ methods::OPTION_FILTER_MAP, methods::OPTION_MAP_OR_NONE, methods::OR_FUN_CALL, + methods::OR_THEN_UNWRAP, methods::RESULT_MAP_OR_INTO_OPTION, methods::SEARCH_IS_SOME, methods::SHOULD_IMPLEMENT_TRAIT, @@ -332,6 +334,7 @@ store.register_lints(&[ methods::UNNECESSARY_FILTER_MAP, methods::UNNECESSARY_FIND_MAP, methods::UNNECESSARY_FOLD, + methods::UNNECESSARY_JOIN, methods::UNNECESSARY_LAZY_EVALUATIONS, methods::UNNECESSARY_TO_OWNED, methods::UNWRAP_OR_ELSE_DEFAULT, diff --git a/clippy_lints/src/lib.register_nursery.rs b/clippy_lints/src/lib.register_nursery.rs index 8d4dde42b..c2fc67afb 100644 --- a/clippy_lints/src/lib.register_nursery.rs +++ b/clippy_lints/src/lib.register_nursery.rs @@ -13,6 +13,7 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![ LintId::of(future_not_send::FUTURE_NOT_SEND), LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE), LintId::of(let_if_seq::USELESS_LET_IF_SEQ), + LintId::of(methods::ITER_WITH_DRAIN), 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_ATOMIC), diff --git a/clippy_lints/src/lib.register_pedantic.rs b/clippy_lints/src/lib.register_pedantic.rs index 00d305131..eb6534cb8 100644 --- a/clippy_lints/src/lib.register_pedantic.rs +++ b/clippy_lints/src/lib.register_pedantic.rs @@ -63,6 +63,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(methods::IMPLICIT_CLONE), LintId::of(methods::INEFFICIENT_TO_STRING), LintId::of(methods::MAP_UNWRAP_OR), + LintId::of(methods::UNNECESSARY_JOIN), LintId::of(misc::FLOAT_CMP), LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(mut_mut::MUT_MUT), diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 6e9c0ee33..f2f5c988d 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -16,7 +16,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(methods::EXTEND_WITH_DRAIN), LintId::of(methods::ITER_NTH), LintId::of(methods::ITER_OVEREAGER_CLONED), - LintId::of(methods::ITER_WITH_DRAIN), LintId::of(methods::MANUAL_STR_REPEAT), LintId::of(methods::OR_FUN_CALL), LintId::of(methods::SINGLE_CHAR_PATTERN), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 4e30fc381..6ab139b2f 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -62,6 +62,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(strings::STRING_SLICE), LintId::of(strings::STRING_TO_STRING), LintId::of(strings::STR_TO_STRING), + LintId::of(try_err::TRY_ERR), LintId::of(types::RC_BUFFER), LintId::of(types::RC_MUTEX), LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS), diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 05211476f..dcf399cf9 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -105,7 +105,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS), LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS), LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME), - LintId::of(try_err::TRY_ERR), LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(unused_unit::UNUSED_UNIT), LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS), diff --git a/clippy_lints/src/lib.register_suspicious.rs b/clippy_lints/src/lib.register_suspicious.rs index 465baa825..fa3a88e13 100644 --- a/clippy_lints/src/lib.register_suspicious.rs +++ b/clippy_lints/src/lib.register_suspicious.rs @@ -7,6 +7,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec! LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), + LintId::of(casts::CAST_ENUM_CONSTRUCTOR), LintId::of(casts::CAST_ENUM_TRUNCATION), LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE), LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 504235d0d..f2a079991 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -23,6 +23,7 @@ // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) +extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_attr; diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index d11dda57e..b8591fe0d 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,19 +1,66 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash}; -use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdSet, Pat, PatKind}; +use core::cmp::Ordering; +use core::iter; +use core::slice; +use rustc_arena::DroplessArena; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd}; use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Symbol; use std::collections::hash_map::Entry; use super::MATCH_SAME_ARMS; -pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { +#[allow(clippy::too_many_lines)] +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { let mut h = SpanlessHash::new(cx); h.hash_expr(arm.body); h.finish() }; + let arena = DroplessArena::default(); + let normalized_pats: Vec<_> = arms + .iter() + .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat)) + .collect(); + + // The furthast forwards a pattern can move without semantic changes + let forwards_blocking_idxs: Vec<_> = normalized_pats + .iter() + .enumerate() + .map(|(i, pat)| { + normalized_pats[i + 1..] + .iter() + .enumerate() + .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j)) + .unwrap_or(normalized_pats.len()) + }) + .collect(); + + // The furthast backwards a pattern can move without semantic changes + let backwards_blocking_idxs: Vec<_> = normalized_pats + .iter() + .enumerate() + .map(|(i, pat)| { + normalized_pats[..i] + .iter() + .enumerate() + .rev() + .zip(forwards_blocking_idxs[..i].iter().copied().rev()) + .skip_while(|&(_, forward_block)| forward_block > i) + .find_map(|((j, other), forward_block)| { + (forward_block == i || pat.has_overlapping_values(other)).then(|| j) + }) + .unwrap_or(0) + }) + .collect(); + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { let min_index = usize::min(lindex, rindex); let max_index = usize::max(lindex, rindex); @@ -42,53 +89,316 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { } }; // Arms with a guard are ignored, those can’t always be merged together - // This is also the case for arms in-between each there is an arm with a guard - (min_index..=max_index).all(|index| arms[index].guard.is_none()) - && SpanlessEq::new(cx) - .expr_fallback(eq_fallback) - .eq_expr(lhs.body, rhs.body) - // these checks could be removed to allow unused bindings - && bindings_eq(lhs.pat, local_map.keys().copied().collect()) - && bindings_eq(rhs.pat, local_map.values().copied().collect()) + // If both arms overlap with an arm in between then these can't be merged either. + !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index) + && lhs.guard.is_none() + && rhs.guard.is_none() + && SpanlessEq::new(cx) + .expr_fallback(eq_fallback) + .eq_expr(lhs.body, rhs.body) + // these checks could be removed to allow unused bindings + && bindings_eq(lhs.pat, local_map.keys().copied().collect()) + && bindings_eq(rhs.pat, local_map.values().copied().collect()) }; let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); - for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { - span_lint_and_then( - cx, - MATCH_SAME_ARMS, - j.body.span, - "this `match` has identical arm bodies", - |diag| { - diag.span_note(i.body.span, "same as this"); + for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { + if matches!(arm2.pat.kind, PatKind::Wild) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + arm1.span, + "this match arm has an identical body to the `_` wildcard arm", + |diag| { + diag.span_suggestion( + arm1.span, + "try removing the arm", + String::new(), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(arm2.span, "`_` wildcard arm here"); + }, + ); + } else { + let back_block = backwards_blocking_idxs[j]; + let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) { + (arm1, arm2) + } else { + (arm2, arm1) + }; - // Note: this does not use `span_suggestion` on purpose: - // there is no clean way - // to remove the other arm. Building a span and suggest to replace it to "" - // makes an even more confusing error message. Also in order not to make up a - // span for the whole pattern, the suggestion is only shown when there is only - // one pattern. The user should know about `|` if they are already using it… + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + keep_arm.span, + "this match arm has an identical body to another arm", + |diag| { + let move_pat_snip = snippet(cx, move_arm.pat.span, ""); + let keep_pat_snip = snippet(cx, keep_arm.pat.span, ""); - let lhs = snippet(cx, i.pat.span, ""); - let rhs = snippet(cx, j.pat.span, ""); + diag.span_suggestion( + keep_arm.pat.span, + "try merging the arm patterns", + format!("{} | {}", keep_pat_snip, move_pat_snip), + Applicability::MaybeIncorrect, + ) + .help("or try changing either arm body") + .span_note(move_arm.span, "other arm here"); + }, + ); + } + } +} - if let PatKind::Wild = j.pat.kind { - // if the last arm is _, then i could be integrated into _ - // note that i.pat cannot be _, because that would mean that we're - // hiding all the subsequent arms, and rust won't compile - diag.span_note( - i.body.span, - &format!( - "`{}` has the same arm body as the `_` wildcard, consider removing it", - lhs - ), - ); - } else { - diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,)) - .help("...or consider changing the match arm bodies"); +#[derive(Clone, Copy)] +enum NormalizedPat<'a> { + Wild, + Struct(Option, &'a [(Symbol, Self)]), + Tuple(Option, &'a [Self]), + Or(&'a [Self]), + Path(Option), + LitStr(Symbol), + LitBytes(&'a [u8]), + LitInt(u128), + LitBool(bool), + Range(PatRange), + /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise + /// the first value contains everything before the `..` wildcard pattern, and the second value + /// contains everything afterwards. Note that either side, or both sides, may contain zero + /// patterns. + Slice(&'a [Self], Option<&'a [Self]>), +} + +#[derive(Clone, Copy)] +struct PatRange { + start: u128, + end: u128, + bounds: RangeEnd, +} +impl PatRange { + fn contains(&self, x: u128) -> bool { + x >= self.start + && match self.bounds { + RangeEnd::Included => x <= self.end, + RangeEnd::Excluded => x < self.end, + } + } + + fn overlaps(&self, other: &Self) -> bool { + // Note: Empty ranges are impossible, so this is correct even though it would return true if an + // empty exclusive range were to reside within an inclusive range. + (match self.bounds { + RangeEnd::Included => self.end >= other.start, + RangeEnd::Excluded => self.end > other.start, + } && match other.bounds { + RangeEnd::Included => self.start <= other.end, + RangeEnd::Excluded => self.start < other.end, + }) + } +} + +/// Iterates over the pairs of fields with matching names. +fn iter_matching_struct_fields<'a>( + left: &'a [(Symbol, NormalizedPat<'a>)], + right: &'a [(Symbol, NormalizedPat<'a>)], +) -> impl Iterator, &'a NormalizedPat<'a>)> + 'a { + struct Iter<'a>( + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + slice::Iter<'a, (Symbol, NormalizedPat<'a>)>, + ); + impl<'a> Iterator for Iter<'a> { + type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>); + fn next(&mut self) -> Option { + // Note: all the fields in each slice are sorted by symbol value. + let mut left = self.0.next()?; + let mut right = self.1.next()?; + loop { + match left.0.cmp(&right.0) { + Ordering::Equal => return Some((&left.1, &right.1)), + Ordering::Less => left = self.0.next()?, + Ordering::Greater => right = self.1.next()?, } + } + } + } + Iter(left.iter(), right.iter()) +} + +#[allow(clippy::similar_names)] +impl<'a> NormalizedPat<'a> { + #[allow(clippy::too_many_lines)] + fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self { + match pat.kind { + PatKind::Wild | PatKind::Binding(.., None) => Self::Wild, + PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => { + Self::from_pat(cx, arena, pat) }, - ); + PatKind::Struct(ref path, fields, _) => { + let fields = + arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat)))); + fields.sort_by_key(|&(name, _)| name); + Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields) + }, + PatKind::TupleStruct(ref path, pats, wild_idx) => { + let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() { + Some(x) => x, + None => return Self::Wild, + }; + let (var_id, variant) = if adt.is_enum() { + match cx.qpath_res(path, pat.hir_id).opt_def_id() { + Some(x) => (Some(x), adt.variant_with_ctor_id(x)), + None => return Self::Wild, + } + } else { + (None, adt.non_enum_variant()) + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(var_id, pats) + }, + PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))), + PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()), + PatKind::Tuple(pats, wild_idx) => { + let field_count = match cx.typeck_results().pat_ty(pat).kind() { + ty::Tuple(subs) => subs.len(), + _ => return Self::Wild, + }; + let (front, back) = match wild_idx { + Some(i) => pats.split_at(i), + None => (pats, [].as_slice()), + }; + let pats = arena.alloc_from_iter( + front + .iter() + .map(|pat| Self::from_pat(cx, arena, pat)) + .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len())) + .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))), + ); + Self::Tuple(None, pats) + }, + PatKind::Lit(e) => match &e.kind { + // TODO: Handle negative integers. They're currently treated as a wild match. + ExprKind::Lit(lit) => match lit.node { + LitKind::Str(sym, _) => Self::LitStr(sym), + LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes), + LitKind::Byte(val) => Self::LitInt(val.into()), + LitKind::Char(val) => Self::LitInt(val.into()), + LitKind::Int(val, _) => Self::LitInt(val), + LitKind::Bool(val) => Self::LitBool(val), + LitKind::Float(..) | LitKind::Err(_) => Self::Wild, + }, + _ => Self::Wild, + }, + PatKind::Range(start, end, bounds) => { + // TODO: Handle negative integers. They're currently treated as a wild match. + let start = match start { + None => 0, + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => val, + LitKind::Char(val) => val.into(), + LitKind::Byte(val) => val.into(), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + let (end, bounds) = match end { + None => (u128::MAX, RangeEnd::Included), + Some(e) => match &e.kind { + ExprKind::Lit(lit) => match lit.node { + LitKind::Int(val, _) => (val, bounds), + LitKind::Char(val) => (val.into(), bounds), + LitKind::Byte(val) => (val.into(), bounds), + _ => return Self::Wild, + }, + _ => return Self::Wild, + }, + }; + Self::Range(PatRange { start, end, bounds }) + }, + PatKind::Slice(front, wild_pat, back) => Self::Slice( + arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))), + wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))), + ), + } + } + + /// Checks if two patterns overlap in the values they can match assuming they are for the same + /// type. + fn has_overlapping_values(&self, other: &Self) -> bool { + match (*self, *other) { + (Self::Wild, _) | (_, Self::Wild) => true, + (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => { + pats.iter().any(|pat| pat.has_overlapping_values(other)) + }, + (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => { + if lpath != rpath { + return false; + } + iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + }, + (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => { + if lpath != rpath { + return false; + } + lpats + .iter() + .zip(rpats.iter()) + .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat)) + }, + (Self::Path(x), Self::Path(y)) => x == y, + (Self::LitStr(x), Self::LitStr(y)) => x == y, + (Self::LitBytes(x), Self::LitBytes(y)) => x == y, + (Self::LitInt(x), Self::LitInt(y)) => x == y, + (Self::LitBool(x), Self::LitBool(y)) => x == y, + (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y), + (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x), + (Self::Slice(lpats, None), Self::Slice(rpats, None)) => { + lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y)) + }, + (Self::Slice(pats, None), Self::Slice(front, Some(back))) + | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { + // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater + // then the minium length required will be greater than the length of `pats`. + if pats.len() < front.len() + back.len() { + return false; + } + pats[..front.len()] + .iter() + .zip(front.iter()) + .chain(pats[pats.len() - back.len()..].iter().zip(back.iter())) + .all(|(x, y)| x.has_overlapping_values(y)) + }, + (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront + .iter() + .zip(rfront.iter()) + .chain(lback.iter().rev().zip(rback.iter().rev())) + .all(|(x, y)| x.has_overlapping_values(y)), + + // Enums can mix unit variants with tuple/struct variants. These can never overlap. + (Self::Path(_), Self::Tuple(..) | Self::Struct(..)) + | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false, + + // Tuples can be matched like a struct. + (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => { + // TODO: check fields here. + x == y + }, + + // TODO: Lit* with Path, Range with Path, LitBytes with Slice + _ => true, + } } } diff --git a/clippy_lints/src/methods/map_flatten.rs b/clippy_lints/src/methods/map_flatten.rs index e1212c31c..f447940ea 100644 --- a/clippy_lints/src/methods/map_flatten.rs +++ b/clippy_lints/src/methods/map_flatten.rs @@ -1,83 +1,73 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_sugg_for_edges; use clippy_utils::is_trait_method; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; -use rustc_span::symbol::sym; +use rustc_span::{symbol::sym, Span}; use super::MAP_FLATTEN; /// lint use of `map().flatten()` for `Iterators` and 'Options' -pub(super) fn check<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - map_arg: &'tcx hir::Expr<'_>, -) { - // lint if caller of `.map().flatten()` is an Iterator - if is_trait_method(cx, expr, sym::Iterator) { - let map_closure_ty = cx.typeck_results().expr_ty(map_arg); - let is_map_to_option = match map_closure_ty.kind() { - ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { - let map_closure_sig = match map_closure_ty.kind() { - ty::Closure(_, substs) => substs.as_closure().sig(), - _ => map_closure_ty.fn_sig(cx.tcx), - }; - let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); - is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) - }, - _ => false, - }; - - let method_to_use = if is_map_to_option { - // `(...).map(...)` has type `impl Iterator> - "filter_map" - } else { - // `(...).map(...)` has type `impl Iterator> - "flat_map" - }; - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".{0}({1})", method_to_use, func_snippet); - span_lint_and_sugg( +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) { + if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) { + let mut applicability = Applicability::MachineApplicable; + let help_msgs = [ + &format!("try replacing `map` with `{}`", method_to_use), + "and remove the `.flatten()`", + ]; + let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability); + span_lint_and_sugg_for_edges( cx, MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - "called `map(..).flatten()` on an `Iterator`", - &format!("try using `{}` instead", method_to_use), - hint, - Applicability::MachineApplicable, + expr.span.with_lo(map_span.lo()), + &format!("called `map(..).flatten()` on `{}`", caller_ty_name), + &help_msgs, + format!("{}({})", method_to_use, closure_snippet), + applicability, ); } - - // lint if caller of `.map().flatten()` is an Option or Result - let caller_type = match cx.typeck_results().expr_ty(recv).kind() { - ty::Adt(adt, _) => { - if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { - "Option" - } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { - "Result" - } else { - return; - } - }, - _ => { - return; - }, - }; - - let func_snippet = snippet(cx, map_arg.span, ".."); - let hint = format!(".and_then({})", func_snippet); - let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type); - span_lint_and_sugg( - cx, - MAP_FLATTEN, - expr.span.with_lo(recv.span.hi()), - &lint_info, - "try using `and_then` instead", - hint, - Applicability::MachineApplicable, - ); +} + +fn try_get_caller_ty_name_and_method_name( + cx: &LateContext<'_>, + expr: &Expr<'_>, + caller_expr: &Expr<'_>, + map_arg: &Expr<'_>, +) -> Option<(&'static str, &'static str)> { + if is_trait_method(cx, expr, sym::Iterator) { + if is_map_to_option(cx, map_arg) { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "filter_map")) + } else { + // `(...).map(...)` has type `impl Iterator> + Some(("Iterator", "flat_map")) + } + } else { + if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { + if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { + return Some(("Option", "and_then")); + } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { + return Some(("Result", "and_then")); + } + } + None + } +} + +fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool { + let map_closure_ty = cx.typeck_results().expr_ty(map_arg); + match map_closure_ty.kind() { + ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => { + let map_closure_sig = match map_closure_ty.kind() { + ty::Closure(_, substs) => substs.as_closure().sig(), + _ => map_closure_ty.fn_sig(cx.tcx), + }; + let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output()); + is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option) + }, + _ => false, + } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 5edd22cd1..9d4e1fa39 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -45,6 +45,7 @@ mod option_as_ref_deref; mod option_map_or_none; mod option_map_unwrap_or; mod or_fun_call; +mod or_then_unwrap; mod search_is_some; mod single_char_add_str; mod single_char_insert_string; @@ -59,6 +60,7 @@ mod uninit_assumed_init; mod unnecessary_filter_map; mod unnecessary_fold; mod unnecessary_iter_cloned; +mod unnecessary_join; mod unnecessary_lazy_eval; mod unnecessary_to_owned; mod unwrap_or_else_default; @@ -778,6 +780,42 @@ declare_clippy_lint! { "using any `*or` method with a function call, which suggests `*or_else`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for `.or(…).unwrap()` calls to Options and Results. + /// + /// ### Why is this bad? + /// You should use `.unwrap_or(…)` instead for clarity. + /// + /// ### Example + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # type Error = &'static str; + /// # let result: Result<&str, Error> = Err("error"); + /// let value = result.or::(Ok(fallback)).unwrap(); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.or(Some(fallback)).unwrap(); + /// ``` + /// Use instead: + /// ```rust + /// # let fallback = "fallback"; + /// // Result + /// # let result: Result<&str, &str> = Err("error"); + /// let value = result.unwrap_or(fallback); + /// + /// // Option + /// # let option: Option<&str> = None; + /// let value = option.unwrap_or(fallback); + /// ``` + #[clippy::version = "1.61.0"] + pub OR_THEN_UNWRAP, + complexity, + "checks for `.or(…).unwrap()` calls to Options and Results." +} + declare_clippy_lint! { /// ### What it does /// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`, @@ -1140,7 +1178,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.61.0"] pub ITER_WITH_DRAIN, - perf, + nursery, "replace `.drain(..)` with `.into_iter()`" } @@ -2012,6 +2050,35 @@ declare_clippy_lint! { "unnecessary calls to `to_owned`-like functions" } +declare_clippy_lint! { + /// ### What it does + /// Checks for use of `.collect::>().join("")` on iterators. + /// + /// ### Why is this bad? + /// `.collect::()` is more concise and usually more performant + /// + /// ### Example + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::>().join(""); + /// println!("{}", output); + /// ``` + /// The correct use would be: + /// ```rust + /// let vector = vec!["hello", "world"]; + /// let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + /// println!("{}", output); + /// ``` + /// ### Known problems + /// While `.collect::()` is more performant in most cases, there are cases where + /// using `.collect::()` over `.collect::>().join("")` + /// will prevent loop unrolling and will result in a negative performance impact. + #[clippy::version = "1.61.0"] + pub UNNECESSARY_JOIN, + pedantic, + "using `.collect::>().join(\"\")` on an iterator" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, @@ -2039,6 +2106,7 @@ impl_lint_pass!(Methods => [ OPTION_MAP_OR_NONE, BIND_INSTEAD_OF_MAP, OR_FUN_CALL, + OR_THEN_UNWRAP, EXPECT_FUN_CALL, CHARS_NEXT_CMP, CHARS_LAST_CMP, @@ -2096,6 +2164,7 @@ impl_lint_pass!(Methods => [ MANUAL_SPLIT_ONCE, NEEDLESS_SPLITN, UNNECESSARY_TO_OWNED, + UNNECESSARY_JOIN, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2377,7 +2446,7 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio flat_map_option::check(cx, expr, arg, span); }, (name @ "flatten", args @ []) => match method_call(recv) { - Some(("map", [recv, map_arg], _)) => map_flatten::check(cx, expr, recv, map_arg), + Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span), Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), _ => {}, }, @@ -2391,6 +2460,11 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio ("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_none", []) => check_is_some_is_none(cx, expr, recv, false), ("is_some", []) => check_is_some_is_none(cx, expr, recv, true), + ("join", [join_arg]) => { + if let Some(("collect", _, span)) = method_call(recv) { + unnecessary_join::check(cx, expr, recv, join_arg, span); + } + }, ("last", args @ []) | ("skip", args @ [_]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { @@ -2474,6 +2548,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio Some(("get_mut", [recv, get_arg], _)) => { get_unwrap::check(cx, expr, recv, get_arg, true); }, + Some(("or", [recv, or_arg], or_span)) => { + or_then_unwrap::check(cx, expr, recv, or_arg, or_span); + }, _ => {}, } unwrap_used::check(cx, expr, recv); diff --git a/clippy_lints/src/methods/or_then_unwrap.rs b/clippy_lints/src/methods/or_then_unwrap.rs new file mode 100644 index 000000000..be5768c35 --- /dev/null +++ b/clippy_lints/src/methods/or_then_unwrap.rs @@ -0,0 +1,68 @@ +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor}; +use rustc_errors::Applicability; +use rustc_hir::{lang_items::LangItem, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::{sym, Span}; + +use super::OR_THEN_UNWRAP; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + unwrap_expr: &Expr<'_>, + recv: &'tcx Expr<'tcx>, + or_arg: &'tcx Expr<'_>, + or_span: Span, +) { + let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result) + let title; + let or_arg_content: Span; + + if is_type_diagnostic_item(cx, ty, sym::Option) { + title = "found `.or(Some(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) { + or_arg_content = content; + } else { + return; + } + } else if is_type_diagnostic_item(cx, ty, sym::Result) { + title = "found `.or(Ok(…)).unwrap()`"; + if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) { + or_arg_content = content; + } else { + return; + } + } else { + // Someone has implemented a struct with .or(...).unwrap() chaining, + // but it's not an Option or a Result, so bail + return; + } + + let mut applicability = Applicability::MachineApplicable; + let suggestion = format!( + "unwrap_or({})", + snippet_with_applicability(cx, or_arg_content, "..", &mut applicability) + ); + + span_lint_and_sugg( + cx, + OR_THEN_UNWRAP, + unwrap_expr.span.with_lo(or_span.lo()), + title, + "try this", + suggestion, + applicability, + ); +} + +fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option { + if let ExprKind::Call(some_expr, [arg]) = expr.kind + && let ExprKind::Path(qpath) = &some_expr.kind + && is_lang_ctor(cx, qpath, item) + { + Some(arg.span) + } else { + None + } +} diff --git a/clippy_lints/src/methods/unnecessary_join.rs b/clippy_lints/src/methods/unnecessary_join.rs new file mode 100644 index 000000000..973b8a7e6 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_join.rs @@ -0,0 +1,41 @@ +use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{Ref, Slice}; +use rustc_span::{sym, Span}; + +use super::UNNECESSARY_JOIN; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + join_self_arg: &'tcx Expr<'tcx>, + join_arg: &'tcx Expr<'tcx>, + span: Span, +) { + let applicability = Applicability::MachineApplicable; + let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg); + if_chain! { + // the turbofish for collect is ::> + if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind(); + if let Slice(slice) = ref_type.kind(); + if is_type_diagnostic_item(cx, *slice, sym::String); + // the argument for join is "" + if let ExprKind::Lit(spanned) = &join_arg.kind; + if let LitKind::Str(symbol, _) = spanned.node; + if symbol.is_empty(); + then { + span_lint_and_sugg( + cx, + UNNECESSARY_JOIN, + span.with_hi(expr.span.hi()), + r#"called `.collect>().join("")` on an iterator"#, + "try using", + "collect::()".to_owned(), + applicability, + ); + } + } +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 1e2765263..2369be708 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{eager_or_lazy, usage}; @@ -48,20 +48,19 @@ pub(super) fn check<'tcx>( Applicability::MaybeIncorrect }; - span_lint_and_sugg( - cx, - UNNECESSARY_LAZY_EVALUATIONS, - expr.span, - msg, - &format!("use `{}` instead", simplify_using), - format!( - "{0}.{1}({2})", - snippet(cx, recv.span, ".."), - simplify_using, - snippet(cx, body_expr.span, ".."), - ), - applicability, - ); + // This is a duplicate of what's happening in clippy_lints::methods::method_call, + // which isn't ideal, We want to get the method call span, + // but prefer to avoid changing the signature of the function itself. + if let hir::ExprKind::MethodCall(_, _, span) = expr.kind { + span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { + diag.span_suggestion( + span, + &format!("use `{}(..)` instead", simplify_using), + format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")), + applicability, + ); + }); + } } } } diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 7916fb8e3..1555758fc 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -2,7 +2,9 @@ use super::implicit_clone::is_clone_like; use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs}; +use clippy_utils::ty::{ + contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs, +}; use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item}; use rustc_errors::Applicability; use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind}; @@ -114,7 +116,12 @@ fn check_addr_of_expr( parent.span, &format!("unnecessary use of `{}`", method_name), "use", - format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs), + format!( + "{:&>width$}{}", + "", + receiver_snippet, + width = n_target_refs - n_receiver_refs + ), Applicability::MachineApplicable, ); return true; @@ -182,20 +189,10 @@ fn check_into_iter_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { - if unnecessary_iter_cloned::check_for_loop_iter( - cx, - parent, - method_name, - receiver, - true, - ) { + if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } - let cloned_or_copied = if is_copy(cx, item_ty) { - "copied" - } else { - "cloned" - }; + let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" }; // The next suggestion may be incorrect because the removal of the `to_owned`-like // function could cause the iterator to hold a reference to a resource that is used // mutably. See https://github.com/rust-lang/rust-clippy/issues/8148. @@ -243,10 +240,11 @@ fn check_other_call_arg<'tcx>( if if trait_predicate.def_id() == deref_trait_id { if let [projection_predicate] = projection_predicates[..] { let normalized_ty = - cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); + cx.tcx + .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term); implements_trait(cx, receiver_ty, deref_trait_id, &[]) - && get_associated_type(cx, receiver_ty, deref_trait_id, - "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) + && get_associated_type(cx, receiver_ty, deref_trait_id, "Target") + .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty) } else { false } @@ -254,7 +252,7 @@ fn check_other_call_arg<'tcx>( let composed_substs = compose_substs( cx, &trait_predicate.trait_ref.substs.iter().skip(1).collect::>()[..], - call_substs + call_substs, ); implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs) } else { @@ -264,6 +262,12 @@ fn check_other_call_arg<'tcx>( // `Target = T`. if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id; let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 }); + // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then + // `T` must not be instantiated with a reference + // (https://github.com/rust-lang/rust-clippy/issues/8507). + if (n_refs == 0 && !receiver_ty.is_ref()) + || trait_predicate.def_id() != as_ref_trait_id + || !contains_ty(fn_sig.output(), input); if let Some(receiver_snippet) = snippet_opt(cx, receiver.span); then { span_lint_and_sugg( @@ -339,11 +343,7 @@ fn get_input_traits_and_projections<'tcx>( if let Some(arg) = substs.iter().next(); if let GenericArgKind::Type(arg_ty) = arg.unpack(); if arg_ty == input; - then { - true - } else { - false - } + then { true } else { false } } }; match predicate.kind().skip_binder() { diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 9c776437d..ba1997e70 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -436,7 +436,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( DerefTy::Path, None, ), - Some(sym::Cow) => { + Some(sym::Cow) if mutability == Mutability::Not => { let ty_name = name.args .and_then(|args| { args.args.iter().find_map(|a| match a { diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 961cdb317..66b795130 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; -use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind}; +use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -76,14 +76,13 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { ); } - for single_use in &single_use_usages { - if !imports_reused_with_self.contains(&single_use.0) { - let can_suggest = single_use.2; + for (name, span, can_suggest) in single_use_usages { + if !imports_reused_with_self.contains(&name) { if can_suggest { span_lint_and_sugg( cx, SINGLE_COMPONENT_PATH_IMPORTS, - single_use.1, + span, "this import is redundant", "remove it entirely", String::new(), @@ -93,7 +92,7 @@ fn check_mod(cx: &EarlyContext<'_>, items: &[P]) { span_lint_and_help( cx, SINGLE_COMPONENT_PATH_IMPORTS, - single_use.1, + span, "this import is redundant", None, "remove this import", @@ -124,14 +123,11 @@ fn track_uses( ItemKind::Use(use_tree) => { let segments = &use_tree.prefix.segments; - let should_report = - |name: &Symbol| !macros.contains(name) || matches!(item.vis.kind, VisibilityKind::Inherited); - // keep track of `use some_module;` usages if segments.len() == 1 { if let UseTreeKind::Simple(None, _, _) = use_tree.kind { let name = segments[0].ident.name; - if should_report(&name) { + if !macros.contains(&name) { single_use_usages.push((name, item.span, true)); } } @@ -146,7 +142,7 @@ fn track_uses( if segments.len() == 1 { if let UseTreeKind::Simple(None, _, _) = tree.0.kind { let name = segments[0].ident.name; - if should_report(&name) { + if !macros.contains(&name) { single_use_usages.push((name, tree.0.span, false)); } } diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 23cb9d40d..02569bd3a 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -415,7 +415,8 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { // And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers. let const_context = in_constant(cx, e.hir_id); - let from_ty = cx.typeck_results().expr_ty(arg); + let from_ty = cx.typeck_results().expr_ty_adjusted(arg); + // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them. let to_ty = cx.typeck_results().expr_ty(e); // If useless_transmute is triggered, the other lints can be skipped. diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 6edff2240..f5e21267a 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -3,8 +3,8 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_c_void; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::subst::Subst; -use rustc_middle::ty::{self, Ty, TypeAndMut}; +use rustc_middle::ty::subst::{Subst, SubstsRef}; +use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy}; use rustc_span::Span; #[allow(clippy::too_many_lines)] @@ -23,7 +23,8 @@ pub(super) fn check<'tcx>( unsized_ty, to_ty: to_sub_ty, } => match reduce_ty(cx, to_sub_ty) { - ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::TypeErasure => break, + ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(to_sub_ty) => { from_ty = unsized_ty; to_ty = to_sub_ty; @@ -48,7 +49,8 @@ pub(super) fn check<'tcx>( unsized_ty, from_ty: from_sub_ty, } => match reduce_ty(cx, from_sub_ty) { - ReducedTy::IntArray | ReducedTy::TypeErasure => break, + ReducedTy::TypeErasure => break, + ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break, ReducedTy::Ref(from_sub_ty) => { from_ty = from_sub_ty; to_ty = unsized_ty; @@ -123,9 +125,19 @@ pub(super) fn check<'tcx>( from_ty: from_sub_ty, to_ty: to_sub_ty, } => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) { - (ReducedTy::IntArray | ReducedTy::TypeErasure, _) - | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false, + (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false, (ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => { + let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs)) + = (from_ty.kind(), to_ty.kind()) + && from_def == to_def + { + if same_except_params(from_subs, to_subs) { + return false; + } + Some(from_def.did()) + } else { + None + }; span_lint_and_then( cx, TRANSMUTE_UNDEFINED_REPR, @@ -135,21 +147,17 @@ pub(super) fn check<'tcx>( from_ty_orig, to_ty_orig ), |diag| { - if_chain! { - if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def()); - if from_def == to_def; - then { - diag.note(&format!( - "two instances of the same generic type (`{}`) may have different layouts", - cx.tcx.item_name(from_def.did()) - )); - } else { - if from_ty_orig.peel_refs() != from_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); - } - if to_ty_orig.peel_refs() != to_ty { - diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); - } + if let Some(same_adt_did) = same_adt_did { + diag.note(&format!( + "two instances of the same generic type (`{}`) may have different layouts", + cx.tcx.item_name(same_adt_did) + )); + } else { + if from_ty_orig.peel_refs() != from_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", from_ty)); + } + if to_ty_orig.peel_refs() != to_ty { + diag.note(&format!("the contained type `{}` has an undefined layout", to_ty)); } } }, @@ -196,10 +204,13 @@ pub(super) fn check<'tcx>( continue; }, ( - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), - ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_), + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, + ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param, ) - | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break, + | ( + ReducedTy::UnorderedFields(_) | ReducedTy::Param, + ReducedTy::UnorderedFields(_) | ReducedTy::Param, + ) => break, }, } } @@ -263,9 +274,8 @@ enum ReducedTy<'tcx> { UnorderedFields(Ty<'tcx>), /// The type is a reference to the contained type. Ref(Ty<'tcx>), - /// The type is an array of a primitive integer type. These can be used as storage for a value - /// of another type. - IntArray, + /// The type is a generic parameter. + Param, /// Any other type. Other(Ty<'tcx>), } @@ -275,17 +285,18 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> loop { ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty); return match *ty.kind() { - ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray, + ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure, ty::Array(sub_ty, _) | ty::Slice(sub_ty) => { ty = sub_ty; continue; }, ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure, ty::Tuple(args) => { - let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else { + let mut iter = args.iter(); + let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else { return ReducedTy::OrderedFields(ty); }; - if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) { + if iter.all(|ty| is_zero_sized_ty(cx, ty)) { ty = sized_ty; continue; } @@ -313,9 +324,12 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => { ReducedTy::TypeErasure }, + // TODO: Check if the conversion to or from at least one of a union's fields is valid. + ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure, ty::Foreign(_) => ReducedTy::TypeErasure, ty::Ref(_, ty, _) => ReducedTy::Ref(ty), ty::RawPtr(ty) => ReducedTy::Ref(ty.ty), + ty::Param(_) => ReducedTy::Param, _ => ReducedTy::Other(ty), }; } @@ -332,3 +346,27 @@ fn is_zero_sized_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { } } } + +fn is_size_pair(ty: Ty<'_>) -> bool { + if let ty::Tuple(tys) = *ty.kind() + && let [ty1, ty2] = &**tys + { + matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) + && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize)) + } else { + false + } +} + +fn same_except_params(subs1: SubstsRef<'_>, subs2: SubstsRef<'_>) -> bool { + // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as + // `Array<6>` + for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) { + match (ty1.kind(), ty2.kind()) { + (ty::Param(_), _) | (_, ty::Param(_)) => (), + (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (), + _ => return false, + } + } + true +} diff --git a/clippy_lints/src/try_err.rs b/clippy_lints/src/try_err.rs index 80d6f3c63..e108f7be1 100644 --- a/clippy_lints/src/try_err.rs +++ b/clippy_lints/src/try_err.rs @@ -43,7 +43,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.38.0"] pub TRY_ERR, - style, + restriction, "return errors explicitly rather than hiding them behind a `?`" } diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index a617422bb..b3fad6ce7 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -85,7 +85,7 @@ macro_rules! CONFIGURATION_VALUE_TEMPLATE { }; } -const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ +const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [ &["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint_and_help"], &["clippy_utils", "diagnostics", "span_lint_and_note"], @@ -93,6 +93,7 @@ const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint_and_sugg"], &["clippy_utils", "diagnostics", "span_lint_and_then"], &["clippy_utils", "diagnostics", "span_lint_hir_and_then"], + &["clippy_utils", "diagnostics", "span_lint_and_sugg_for_edges"], ]; const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [ ("span_suggestion", false), diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 532bd810a..f3d818cc3 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -581,14 +581,19 @@ impl Write { }; let replacement: String = match lit.token.kind { - LitKind::Integer | LitKind::Float | LitKind::Err => continue, LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => { lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") }, LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => { lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}") }, - LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue, + LitKind::StrRaw(_) + | LitKind::Str + | LitKind::ByteStrRaw(_) + | LitKind::ByteStr + | LitKind::Integer + | LitKind::Float + | LitKind::Err => continue, LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() { "\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"", "\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue, diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index a927788e6..625a53899 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -8,7 +8,7 @@ //! Thank you! //! ~The `INTERNAL_METADATA_COLLECTOR` lint -use rustc_errors::{Applicability, Diagnostic}; +use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic}; use rustc_hir::HirId; use rustc_lint::{LateContext, Lint, LintContext}; use rustc_span::source_map::{MultiSpan, Span}; @@ -213,6 +213,90 @@ pub fn span_lint_and_sugg<'a, T: LintContext>( }); } +/// Like [`span_lint_and_sugg`] with a focus on the edges. The output will either +/// emit single span or multispan suggestion depending on the number of its lines. +/// +/// If the given suggestion string has more lines than the maximum display length defined by +/// [`MAX_SUGGESTION_HIGHLIGHT_LINES`][`rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES`], +/// this function will split the suggestion and span to showcase the change for the top and +/// bottom edge of the code. For normal suggestions, in one display window, the help message +/// will be combined with a colon. +/// +/// Multipart suggestions like the one being created here currently cannot be +/// applied by rustfix (See [rustfix#141](https://github.com/rust-lang/rustfix/issues/141)). +/// Testing rustfix with this lint emission function might require a file with +/// suggestions that can be fixed and those that can't. See +/// [clippy#8520](https://github.com/rust-lang/rust-clippy/pull/8520/files) for +/// an example and of this. +/// +/// # Example for a long suggestion +/// +/// ```text +/// error: called `map(..).flatten()` on `Option` +/// --> $DIR/map_flatten.rs:8:10 +/// | +/// LL | .map(|x| { +/// | __________^ +/// LL | | if x <= 5 { +/// LL | | Some(x) +/// LL | | } else { +/// ... | +/// LL | | }) +/// LL | | .flatten(); +/// | |__________________^ +/// | +/// = note: `-D clippy::map-flatten` implied by `-D warnings` +/// help: try replacing `map` with `and_then` +/// | +/// LL ~ .and_then(|x| { +/// LL + if x <= 5 { +/// LL + Some(x) +/// | +/// help: and remove the `.flatten()` +/// | +/// LL + None +/// LL + } +/// LL ~ }); +/// | +/// ``` +pub fn span_lint_and_sugg_for_edges( + cx: &LateContext<'_>, + lint: &'static Lint, + sp: Span, + msg: &str, + helps: &[&str; 2], + sugg: String, + applicability: Applicability, +) { + span_lint_and_then(cx, lint, sp, msg, |diag| { + let sugg_lines_count = sugg.lines().count(); + if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES { + let sm = cx.sess().source_map(); + if let (Ok(line_upper), Ok(line_bottom)) = (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi())) { + let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2; + let span_upper = sm.span_until_char(sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]), '\n'); + let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]); + + let sugg_lines_vec = sugg.lines().collect::>(); + let sugg_upper = sugg_lines_vec[..split_idx].join("\n"); + let sugg_bottom = sugg_lines_vec[sugg_lines_count - split_idx..].join("\n"); + + diag.span_suggestion(span_upper, helps[0], sugg_upper, applicability); + diag.span_suggestion(span_bottom, helps[1], sugg_bottom, applicability); + + return; + } + } + diag.span_suggestion_with_style( + sp, + &helps.join(", "), + sugg, + applicability, + rustc_errors::SuggestionStyle::ShowAlways, + ); + }); +} + /// Create a suggestion made from several `span → replacement`. /// /// Note: in the JSON format (used by `compiletest_rs`), the help message will diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 63c442e70..1fc9979f3 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -808,7 +808,7 @@ pub fn deref_closure_args<'tcx>(cx: &LateContext<'_>, closure: &'tcx hir::Expr<' closure_arg_is_type_annotated_double_ref, next_pos: closure.span.lo(), suggestion_start: String::new(), - applicability: Applicability::MaybeIncorrect, + applicability: Applicability::MachineApplicable, }; let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id); diff --git a/rust-toolchain b/rust-toolchain index 9d5da4ed6..5befb856a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-03-14" +channel = "nightly-2022-03-24" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui/cast_enum_constructor.rs b/tests/ui/cast_enum_constructor.rs new file mode 100644 index 000000000..0193454ad --- /dev/null +++ b/tests/ui/cast_enum_constructor.rs @@ -0,0 +1,17 @@ +#![warn(clippy::cast_enum_constructor)] +#![allow(clippy::fn_to_numeric_cast)] + +fn main() { + enum Foo { + Y(u32), + } + + enum Bar { + X, + } + + let _ = Foo::Y as usize; + let _ = Foo::Y as isize; + let _ = Foo::Y as fn(u32) -> Foo; + let _ = Bar::X as usize; +} diff --git a/tests/ui/cast_enum_constructor.stderr b/tests/ui/cast_enum_constructor.stderr new file mode 100644 index 000000000..710909dd2 --- /dev/null +++ b/tests/ui/cast_enum_constructor.stderr @@ -0,0 +1,16 @@ +error: cast of an enum tuple constructor to an integer + --> $DIR/cast_enum_constructor.rs:13:13 + | +LL | let _ = Foo::Y as usize; + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::cast-enum-constructor` implied by `-D warnings` + +error: cast of an enum tuple constructor to an integer + --> $DIR/cast_enum_constructor.rs:14:13 + | +LL | let _ = Foo::Y as isize; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/map_flatten.rs b/tests/ui/map_flatten.rs index aa1f76e33..7d47ee09d 100644 --- a/tests/ui/map_flatten.rs +++ b/tests/ui/map_flatten.rs @@ -1,31 +1,55 @@ -// run-rustfix - -#![warn(clippy::all, clippy::pedantic)] -#![allow(clippy::let_underscore_drop)] -#![allow(clippy::missing_docs_in_private_items)] -#![allow(clippy::map_identity)] -#![allow(clippy::redundant_closure)] -#![allow(clippy::unnecessary_wraps)] +#![warn(clippy::map_flatten)] #![feature(result_flattening)] -fn main() { - // mapping to Option on Iterator - fn option_id(x: i8) -> Option { - Some(x) - } - let option_id_ref: fn(i8) -> Option = option_id; - let option_id_closure = |x| Some(x); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); - let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); +// issue #8506, multi-line +#[rustfmt::skip] +fn long_span() { + let _: Option = Some(1) + .map(|x| { + if x <= 5 { + Some(x) + } else { + None + } + }) + .flatten(); - // mapping to Iterator on Iterator - let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + let _: Result = Ok(1) + .map(|x| { + if x == 1 { + Ok(x) + } else { + Err(0) + } + }) + .flatten(); - // mapping to Option on Option - let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); - - // mapping to Result on Result - let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); + let result: Result = Ok(2); + fn do_something() { } + let _: Result = result + .map(|res| { + if res > 0 { + do_something(); + Ok(res) + } else { + Err(0) + } + }) + .flatten(); + + let _: Vec<_> = vec![5_i8; 6] + .into_iter() + .map(|some_value| { + if some_value > 3 { + Some(some_value) + } else { + None + } + }) + .flatten() + .collect(); +} + +fn main() { + long_span(); } diff --git a/tests/ui/map_flatten.stderr b/tests/ui/map_flatten.stderr index bcd2047e6..c9c60df83 100644 --- a/tests/ui/map_flatten.stderr +++ b/tests/ui/map_flatten.stderr @@ -1,46 +1,107 @@ -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:18:46 +error: called `map(..).flatten()` on `Option` + --> $DIR/map_flatten.rs:8:10 | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)` +LL | .map(|x| { + | __________^ +LL | | if x <= 5 { +LL | | Some(x) +LL | | } else { +... | +LL | | }) +LL | | .flatten(); + | |__________________^ | = note: `-D clippy::map-flatten` implied by `-D warnings` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:19:46 +help: try replacing `map` with `and_then` | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:20:46 +LL ~ .and_then(|x| { +LL + if x <= 5 { +LL + Some(x) | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:21:46 +help: and remove the `.flatten()` | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))` - -error: called `map(..).flatten()` on an `Iterator` - --> $DIR/map_flatten.rs:24:46 +LL + None +LL + } +LL ~ }); | -LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)` -error: called `map(..).flatten()` on an `Option` - --> $DIR/map_flatten.rs:27:39 +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten.rs:18:10 | -LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); - | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)` - -error: called `map(..).flatten()` on an `Result` - --> $DIR/map_flatten.rs:30:41 +LL | .map(|x| { + | __________^ +LL | | if x == 1 { +LL | | Ok(x) +LL | | } else { +... | +LL | | }) +LL | | .flatten(); + | |__________________^ + | +help: try replacing `map` with `and_then` + | +LL ~ .and_then(|x| { +LL + if x == 1 { +LL + Ok(x) + | +help: and remove the `.flatten()` + | +LL + Err(0) +LL + } +LL ~ }); | -LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); - | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)` -error: aborting due to 7 previous errors +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten.rs:30:10 + | +LL | .map(|res| { + | __________^ +LL | | if res > 0 { +LL | | do_something(); +LL | | Ok(res) +... | +LL | | }) +LL | | .flatten(); + | |__________________^ + | +help: try replacing `map` with `and_then` + | +LL ~ .and_then(|res| { +LL + if res > 0 { +LL + do_something(); + | +help: and remove the `.flatten()` + | +LL + Err(0) +LL + } +LL ~ }); + | + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten.rs:42:10 + | +LL | .map(|some_value| { + | __________^ +LL | | if some_value > 3 { +LL | | Some(some_value) +LL | | } else { +... | +LL | | }) +LL | | .flatten() + | |__________________^ + | +help: try replacing `map` with `filter_map` + | +LL ~ .filter_map(|some_value| { +LL + if some_value > 3 { +LL + Some(some_value) + | +help: and remove the `.flatten()` + | +LL + None +LL + } +LL + }) + | + +error: aborting due to 4 previous errors diff --git a/tests/ui/map_flatten.fixed b/tests/ui/map_flatten_fixable.fixed similarity index 100% rename from tests/ui/map_flatten.fixed rename to tests/ui/map_flatten_fixable.fixed diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs new file mode 100644 index 000000000..aa1f76e33 --- /dev/null +++ b/tests/ui/map_flatten_fixable.rs @@ -0,0 +1,31 @@ +// run-rustfix + +#![warn(clippy::all, clippy::pedantic)] +#![allow(clippy::let_underscore_drop)] +#![allow(clippy::missing_docs_in_private_items)] +#![allow(clippy::map_identity)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::unnecessary_wraps)] +#![feature(result_flattening)] + +fn main() { + // mapping to Option on Iterator + fn option_id(x: i8) -> Option { + Some(x) + } + let option_id_ref: fn(i8) -> Option = option_id; + let option_id_closure = |x| Some(x); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + + // mapping to Iterator on Iterator + let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + + // mapping to Option on Option + let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + + // mapping to Result on Result + let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); +} diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr new file mode 100644 index 000000000..c91c73846 --- /dev/null +++ b/tests/ui/map_flatten_fixable.stderr @@ -0,0 +1,80 @@ +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:18:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-flatten` implied by `-D warnings` +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect(); + | ~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:19:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:20:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:21:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `filter_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:24:47 + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `flat_map`, and remove the `.flatten()` + | +LL | let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect(); + | ~~~~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Option` + --> $DIR/map_flatten_fixable.rs:27:40 + | +LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `and_then`, and remove the `.flatten()` + | +LL | let _: Option<_> = (Some(Some(1))).and_then(|x| x); + | ~~~~~~~~~~~~~~~ + +error: called `map(..).flatten()` on `Result` + --> $DIR/map_flatten_fixable.rs:30:42 + | +LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try replacing `map` with `and_then`, and remove the `.flatten()` + | +LL | let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x); + | ~~~~~~~~~~~~~~~ + +error: aborting due to 7 previous errors + diff --git a/tests/ui/match_same_arms.stderr b/tests/ui/match_same_arms.stderr index 7752a8a6f..b6d04263b 100644 --- a/tests/ui/match_same_arms.stderr +++ b/tests/ui/match_same_arms.stderr @@ -1,128 +1,121 @@ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:13:14 +error: this match arm has an identical body to the `_` wildcard arm + --> $DIR/match_same_arms.rs:11:9 | -LL | _ => 0, //~ ERROR match arms have same body - | ^ +LL | Abc::A => 0, + | ^^^^^^^^^^^ help: try removing the arm | = note: `-D clippy::match-same-arms` implied by `-D warnings` -note: same as this - --> $DIR/match_same_arms.rs:11:19 + = help: or try changing either arm body +note: `_` wildcard arm here + --> $DIR/match_same_arms.rs:13:9 | -LL | Abc::A => 0, - | ^ -note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it - --> $DIR/match_same_arms.rs:11:19 - | -LL | Abc::A => 0, - | ^ +LL | _ => 0, //~ ERROR match arms have same body + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:18:20 - | -LL | (.., 3) => 42, //~ ERROR match arms have same body - | ^^ - | -note: same as this - --> $DIR/match_same_arms.rs:17:23 - | -LL | (1, .., 3) => 42, - | ^^ -help: consider refactoring into `(1, .., 3) | (.., 3)` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms.rs:17:9 | LL | (1, .., 3) => 42, - | ^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | ----------^^^^^^ + | | + | help: try merging the arm patterns: `(1, .., 3) | (.., 3)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:18:9 + | +LL | (.., 3) => 42, //~ ERROR match arms have same body + | ^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:24:15 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:24:9 | LL | 51 => 1, //~ ERROR match arms have same body - | ^ + | --^^^^^ + | | + | help: try merging the arm patterns: `51 | 42` | -note: same as this - --> $DIR/match_same_arms.rs:23:15 - | -LL | 42 => 1, - | ^ -help: consider refactoring into `42 | 51` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:23:9 | LL | 42 => 1, - | ^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:26:15 - | -LL | 52 => 2, //~ ERROR match arms have same body - | ^ - | -note: same as this - --> $DIR/match_same_arms.rs:25:15 - | -LL | 41 => 2, - | ^ -help: consider refactoring into `41 | 52` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms.rs:25:9 | LL | 41 => 2, - | ^^ - = help: ...or consider changing the match arm bodies + | --^^^^^ + | | + | help: try merging the arm patterns: `41 | 52` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:26:9 + | +LL | 52 => 2, //~ ERROR match arms have same body + | ^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:32:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:32:9 | LL | 2 => 2, //~ ERROR 2nd matched arms have same body - | ^ + | -^^^^^ + | | + | help: try merging the arm patterns: `2 | 1` | -note: same as this - --> $DIR/match_same_arms.rs:31:14 - | -LL | 1 => 2, - | ^ -help: consider refactoring into `1 | 2` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:31:9 | LL | 1 => 2, - | ^ - = help: ...or consider changing the match arm bodies + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:33:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:33:9 | LL | 3 => 2, //~ ERROR 3rd matched arms have same body - | ^ + | -^^^^^ + | | + | help: try merging the arm patterns: `3 | 1` | -note: same as this - --> $DIR/match_same_arms.rs:31:14 - | -LL | 1 => 2, - | ^ -help: consider refactoring into `1 | 3` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:31:9 | LL | 1 => 2, - | ^ - = help: ...or consider changing the match arm bodies + | ^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms.rs:50:55 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:32:9 + | +LL | 2 => 2, //~ ERROR 2nd matched arms have same body + | -^^^^^ + | | + | help: try merging the arm patterns: `2 | 3` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms.rs:33:9 + | +LL | 3 => 2, //~ ERROR 3rd matched arms have same body + | ^^^^^^ + +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms.rs:50:17 | LL | CommandInfo::External { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^ + | ----------------------------------^^^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }` | -note: same as this - --> $DIR/match_same_arms.rs:49:54 - | -LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^ -help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms.rs:49:17 | LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index 67e1d5184..dbfeb4379 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -174,4 +174,57 @@ fn main() { Some(2) => 2, _ => 1, }; + + enum Foo { + X(u32), + Y(u32), + Z(u32), + } + + // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) => 1, + _ => 0, + }; + + // Suggest moving `Foo::Z(_)` up. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::X(_) | Foo::Y(_) => 2, + Foo::Z(_) => 1, + _ => 0, + }; + + // Suggest moving `Foo::X(0)` down. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) => 1, + _ => 0, + }; + + // Don't lint. + let _ = match 0 { + -2 => 1, + -5..=50 => 2, + -150..=88 => 1, + _ => 3, + }; + + struct Bar { + x: u32, + y: u32, + z: u32, + } + + // Lint. + let _ = match None { + Some(Bar { x: 0, y: 5, .. }) => 1, + Some(Bar { y: 10, z: 0, .. }) => 2, + None => 50, + Some(Bar { y: 0, x: 5, .. }) => 1, + _ => 200, + }; } diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index e1ed12f93..14a672ba2 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -1,175 +1,138 @@ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:20:14 +error: this match arm has an identical body to the `_` wildcard arm + --> $DIR/match_same_arms2.rs:11:9 | -LL | _ => { - | ______________^ +LL | / 42 => { +LL | | foo(); +LL | | let mut a = 42 + [23].len() as i32; +LL | | if true { +... | +LL | | a +LL | | }, + | |_________^ help: try removing the arm + | + = note: `-D clippy::match-same-arms` implied by `-D warnings` + = help: or try changing either arm body +note: `_` wildcard arm here + --> $DIR/match_same_arms2.rs:20:9 + | +LL | / _ => { LL | | //~ ERROR match arms have same body LL | | foo(); LL | | let mut a = 42 + [23].len() as i32; ... | LL | | a -LL | | }, - | |_________^ - | - = note: `-D clippy::match-same-arms` implied by `-D warnings` -note: same as this - --> $DIR/match_same_arms2.rs:11:15 - | -LL | 42 => { - | _______________^ -LL | | foo(); -LL | | let mut a = 42 + [23].len() as i32; -LL | | if true { -... | -LL | | a -LL | | }, - | |_________^ -note: `42` has the same arm body as the `_` wildcard, consider removing it - --> $DIR/match_same_arms2.rs:11:15 - | -LL | 42 => { - | _______________^ -LL | | foo(); -LL | | let mut a = 42 + [23].len() as i32; -LL | | if true { -... | -LL | | a LL | | }, | |_________^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:34:15 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:34:9 | LL | 51 => foo(), //~ ERROR match arms have same body - | ^^^^^ + | --^^^^^^^^^ + | | + | help: try merging the arm patterns: `51 | 42` | -note: same as this - --> $DIR/match_same_arms2.rs:33:15 - | -LL | 42 => foo(), - | ^^^^^ -help: consider refactoring into `42 | 51` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:33:9 | LL | 42 => foo(), - | ^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:40:17 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:40:9 | LL | None => 24, //~ ERROR match arms have same body - | ^^ + | ----^^^^^^ + | | + | help: try merging the arm patterns: `None | Some(_)` | -note: same as this - --> $DIR/match_same_arms2.rs:39:20 - | -LL | Some(_) => 24, - | ^^ -help: consider refactoring into `Some(_) | None` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:39:9 | LL | Some(_) => 24, - | ^^^^^^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:62:28 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:62:9 | LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body - | ^^^^^^ + | ---------------^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)` | -note: same as this - --> $DIR/match_same_arms2.rs:61:28 - | -LL | (Some(a), None) => bar(a), - | ^^^^^^ -help: consider refactoring into `(Some(a), None) | (None, Some(a))` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:61:9 | LL | (Some(a), None) => bar(a), - | ^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | ^^^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:68:26 - | -LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body - | ^^^^^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:67:26 - | -LL | (Some(a), ..) => bar(a), - | ^^^^^^ -help: consider refactoring into `(Some(a), ..) | (.., Some(a))` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:67:9 | LL | (Some(a), ..) => bar(a), - | ^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies + | -------------^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:68:9 + | +LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body + | ^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:102:29 - | -LL | (Ok(_), Some(x)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^^^^^ - | -note: same as this - --> $DIR/match_same_arms2.rs:101:29 - | -LL | (Ok(x), Some(_)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^^^^^ -help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))` +error: this match arm has an identical body to another arm --> $DIR/match_same_arms2.rs:101:9 | LL | (Ok(x), Some(_)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^ - = help: ...or consider changing the match arm bodies - = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + | ----------------^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:102:9 + | +LL | (Ok(_), Some(x)) => println!("ok {}", x), + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:117:18 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:117:9 | LL | Ok(_) => println!("ok"), - | ^^^^^^^^^^^^^^ + | -----^^^^^^^^^^^^^^^^^^ + | | + | help: try merging the arm patterns: `Ok(_) | Ok(3)` | -note: same as this - --> $DIR/match_same_arms2.rs:116:18 - | -LL | Ok(3) => println!("ok"), - | ^^^^^^^^^^^^^^ -help: consider refactoring into `Ok(3) | Ok(_)` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:116:9 | LL | Ok(3) => println!("ok"), - | ^^^^^ - = help: ...or consider changing the match arm bodies - = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^^^^^^^^^^^^^^^^^^^^^^ -error: this `match` has identical arm bodies - --> $DIR/match_same_arms2.rs:144:14 +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:144:9 | LL | 1 => { - | ______________^ + | ^ help: try merging the arm patterns: `1 | 0` + | _________| + | | LL | | empty!(0); LL | | }, | |_________^ | -note: same as this - --> $DIR/match_same_arms2.rs:141:14 - | -LL | 0 => { - | ______________^ -LL | | empty!(0); -LL | | }, - | |_________^ -help: consider refactoring into `0 | 1` + = help: or try changing either arm body +note: other arm here --> $DIR/match_same_arms2.rs:141:9 | -LL | 0 => { - | ^ - = help: ...or consider changing the match arm bodies +LL | / 0 => { +LL | | empty!(0); +LL | | }, + | |_________^ error: match expression looks like `matches!` macro --> $DIR/match_same_arms2.rs:162:16 @@ -184,5 +147,50 @@ LL | | }; | = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` -error: aborting due to 9 previous errors +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:194:9 + | +LL | Foo::X(0) => 1, + | ---------^^^^^ + | | + | help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:196:9 + | +LL | Foo::Z(_) => 1, + | ^^^^^^^^^^^^^^ + +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:204:9 + | +LL | Foo::Z(_) => 1, + | ---------^^^^^ + | | + | help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:202:9 + | +LL | Foo::X(0) => 1, + | ^^^^^^^^^^^^^^ + +error: this match arm has an identical body to another arm + --> $DIR/match_same_arms2.rs:227:9 + | +LL | Some(Bar { y: 0, x: 5, .. }) => 1, + | ----------------------------^^^^^ + | | + | help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })` + | + = help: or try changing either arm body +note: other arm here + --> $DIR/match_same_arms2.rs:224:9 + | +LL | Some(Bar { x: 0, y: 5, .. }) => 1, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 12 previous errors diff --git a/tests/ui/or_then_unwrap.fixed b/tests/ui/or_then_unwrap.fixed new file mode 100644 index 000000000..27d4b795a --- /dev/null +++ b/tests/ui/or_then_unwrap.fixed @@ -0,0 +1,52 @@ +// run-rustfix + +#![warn(clippy::or_then_unwrap)] +#![allow(clippy::map_identity)] + +struct SomeStruct {} +impl SomeStruct { + fn or(self, _: Option) -> Self { + self + } + fn unwrap(&self) {} +} + +struct SomeOtherStruct {} +impl SomeOtherStruct { + fn or(self) -> Self { + self + } + fn unwrap(&self) {} +} + +fn main() { + let option: Option<&str> = None; + let _ = option.unwrap_or("fallback"); // should trigger lint + + let result: Result<&str, &str> = Err("Error"); + let _ = result.unwrap_or("fallback"); // should trigger lint + + // as part of a method chain + let option: Option<&str> = None; + let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint + + // Not Option/Result + let instance = SomeStruct {}; + let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + + // or takes no argument + let instance = SomeOtherStruct {}; + let _ = instance.or().unwrap(); // should not trigger lint and should not panic + + // None in or + let option: Option<&str> = None; + let _ = option.or(None).unwrap(); // should not trigger lint + + // Not Err in or + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint + + // other function between + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint +} diff --git a/tests/ui/or_then_unwrap.rs b/tests/ui/or_then_unwrap.rs new file mode 100644 index 000000000..0dab5ae2f --- /dev/null +++ b/tests/ui/or_then_unwrap.rs @@ -0,0 +1,52 @@ +// run-rustfix + +#![warn(clippy::or_then_unwrap)] +#![allow(clippy::map_identity)] + +struct SomeStruct {} +impl SomeStruct { + fn or(self, _: Option) -> Self { + self + } + fn unwrap(&self) {} +} + +struct SomeOtherStruct {} +impl SomeOtherStruct { + fn or(self) -> Self { + self + } + fn unwrap(&self) {} +} + +fn main() { + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).unwrap(); // should trigger lint + + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint + + // as part of a method chain + let option: Option<&str> = None; + let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint + + // Not Option/Result + let instance = SomeStruct {}; + let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint + + // or takes no argument + let instance = SomeOtherStruct {}; + let _ = instance.or().unwrap(); // should not trigger lint and should not panic + + // None in or + let option: Option<&str> = None; + let _ = option.or(None).unwrap(); // should not trigger lint + + // Not Err in or + let result: Result<&str, &str> = Err("Error"); + let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint + + // other function between + let option: Option<&str> = None; + let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint +} diff --git a/tests/ui/or_then_unwrap.stderr b/tests/ui/or_then_unwrap.stderr new file mode 100644 index 000000000..da88154c5 --- /dev/null +++ b/tests/ui/or_then_unwrap.stderr @@ -0,0 +1,22 @@ +error: found `.or(Some(…)).unwrap()` + --> $DIR/or_then_unwrap.rs:24:20 + | +LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` + | + = note: `-D clippy::or-then-unwrap` implied by `-D warnings` + +error: found `.or(Ok(…)).unwrap()` + --> $DIR/or_then_unwrap.rs:27:20 + | +LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` + +error: found `.or(Some(…)).unwrap()` + --> $DIR/or_then_unwrap.rs:31:31 + | +LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index 97990fedd..03dd938a2 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -194,3 +194,10 @@ fn two_vecs(a: &mut Vec, b: &mut Vec) { a.push(0); b.push(1); } + +// Issue #8495 +fn cow_conditional_to_mut(a: &mut Cow) { + if a.is_empty() { + a.to_mut().push_str("foo"); + } +} diff --git a/tests/ui/single_component_path_imports_macro.fixed b/tests/ui/single_component_path_imports_macro.fixed deleted file mode 100644 index e43f5d381..000000000 --- a/tests/ui/single_component_path_imports_macro.fixed +++ /dev/null @@ -1,20 +0,0 @@ -// run-rustfix -#![warn(clippy::single_component_path_imports)] -#![allow(unused_imports)] - -// #7106: use statements exporting a macro within a crate should not trigger lint - -macro_rules! m1 { - () => {}; -} -pub(crate) use m1; // ok - -macro_rules! m2 { - () => {}; -} - // fail - -fn main() { - m1!(); - m2!(); -} diff --git a/tests/ui/single_component_path_imports_macro.rs b/tests/ui/single_component_path_imports_macro.rs index 3c65ca305..fda294a61 100644 --- a/tests/ui/single_component_path_imports_macro.rs +++ b/tests/ui/single_component_path_imports_macro.rs @@ -1,8 +1,8 @@ -// run-rustfix #![warn(clippy::single_component_path_imports)] #![allow(unused_imports)] // #7106: use statements exporting a macro within a crate should not trigger lint +// #7923: normal `use` statements of macros should also not trigger the lint macro_rules! m1 { () => {}; @@ -12,7 +12,7 @@ pub(crate) use m1; // ok macro_rules! m2 { () => {}; } -use m2; // fail +use m2; // ok fn main() { m1!(); diff --git a/tests/ui/single_component_path_imports_macro.stderr b/tests/ui/single_component_path_imports_macro.stderr deleted file mode 100644 index 37d517612..000000000 --- a/tests/ui/single_component_path_imports_macro.stderr +++ /dev/null @@ -1,10 +0,0 @@ -error: this import is redundant - --> $DIR/single_component_path_imports_macro.rs:15:1 - | -LL | use m2; // fail - | ^^^^^^^ help: remove it entirely - | - = note: `-D clippy::single-component-path-imports` implied by `-D warnings` - -error: aborting due to previous error - diff --git a/tests/ui/transmute_undefined_repr.rs b/tests/ui/transmute_undefined_repr.rs index b163d6056..b06ed4a91 100644 --- a/tests/ui/transmute_undefined_repr.rs +++ b/tests/ui/transmute_undefined_repr.rs @@ -1,8 +1,9 @@ #![warn(clippy::transmute_undefined_repr)] #![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)] +use core::any::TypeId; use core::ffi::c_void; -use core::mem::{size_of, transmute}; +use core::mem::{size_of, transmute, MaybeUninit}; fn value() -> T { unimplemented!() @@ -87,5 +88,57 @@ fn main() { let _: *const [u8] = transmute(value::>()); // Ok let _: Box<[u8]> = transmute(value::<*mut [u8]>()); // Ok + + let _: Ty2 = transmute(value::<(Ty2,)>()); // Ok + let _: (Ty2,) = transmute(value::>()); // Ok + + let _: Ty2 = transmute(value::<(Ty2, ())>()); // Ok + let _: (Ty2, ()) = transmute(value::>()); // Ok + + let _: Ty2 = transmute(value::<((), Ty2)>()); // Ok + let _: ((), Ty2) = transmute(value::>()); // Ok + + let _: (usize, usize) = transmute(value::<&[u8]>()); // Ok + let _: &[u8] = transmute(value::<(usize, usize)>()); // Ok + + trait Trait {} + let _: (isize, isize) = transmute(value::<&dyn Trait>()); // Ok + let _: &dyn Trait = transmute(value::<(isize, isize)>()); // Ok + + let _: MaybeUninit> = transmute(value::>()); // Ok + let _: Ty2 = transmute(value::>>()); // Ok + + let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec>()); // Ok + } +} + +fn _with_generics() { + if TypeId::of::() != TypeId::of::() || TypeId::of::() != TypeId::of::() { + return; + } + unsafe { + let _: &u32 = transmute(value::<&T>()); // Ok + let _: &T = transmute(value::<&u32>()); // Ok + + let _: Vec = transmute(value::>()); // Ok + let _: Vec = transmute(value::>()); // Ok + + let _: Ty<&u32> = transmute(value::<&T>()); // Ok + let _: Ty<&T> = transmute(value::<&u32>()); // Ok + + let _: Vec = transmute(value::>()); // Ok + let _: Vec = transmute(value::>()); // Ok + + let _: &Ty2 = transmute(value::<&Ty2>()); // Ok + let _: &Ty2 = transmute(value::<&Ty2>()); // Ok + + let _: Vec> = transmute(value::>>()); // Ok + let _: Vec> = transmute(value::>>()); // Ok + + let _: Vec> = transmute(value::>>()); // Err + let _: Vec> = transmute(value::>>()); // Err + + let _: *const u32 = transmute(value::>()); // Ok + let _: Box = transmute(value::<*const u32>()); // Ok } } diff --git a/tests/ui/transmute_undefined_repr.stderr b/tests/ui/transmute_undefined_repr.stderr index 42d544fc9..28bfba6c7 100644 --- a/tests/ui/transmute_undefined_repr.stderr +++ b/tests/ui/transmute_undefined_repr.stderr @@ -1,5 +1,5 @@ error: transmute from `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:26:33 + --> $DIR/transmute_undefined_repr.rs:27:33 | LL | let _: Ty2C = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ LL | let _: Ty2C = transmute(value::>()); // Lin = note: `-D clippy::transmute-undefined-repr` implied by `-D warnings` error: transmute into `Ty2` which has an undefined layout - --> $DIR/transmute_undefined_repr.rs:27:32 + --> $DIR/transmute_undefined_repr.rs:28:32 | LL | let _: Ty2 = transmute(value::>()); // Lint, Ty2 is unordered | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: transmute from `Ty>` to `Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:32:32 + --> $DIR/transmute_undefined_repr.rs:33:32 | LL | let _: Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -21,7 +21,7 @@ LL | let _: Ty2 = transmute(value::>>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty2` to `Ty>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:33:36 + --> $DIR/transmute_undefined_repr.rs:34:36 | LL | let _: Ty> = transmute(value::>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -29,7 +29,7 @@ LL | let _: Ty> = transmute(value::>()); // = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `Ty<&Ty2>` to `&Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:38:33 + --> $DIR/transmute_undefined_repr.rs:39:33 | LL | let _: &Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL | let _: &Ty2 = transmute(value::>>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `&Ty2` to `Ty<&Ty2>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:39:37 + --> $DIR/transmute_undefined_repr.rs:40:37 | LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -45,7 +45,7 @@ LL | let _: Ty<&Ty2> = transmute(value::<&Ty2>()); / = note: two instances of the same generic type (`Ty2`) may have different layouts error: transmute from `std::boxed::Box>` to `&mut Ty2`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:56:45 + --> $DIR/transmute_undefined_repr.rs:57:45 | LL | let _: &'static mut Ty2 = transmute(value::>>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -53,12 +53,28 @@ LL | let _: &'static mut Ty2 = transmute(value::` to `std::boxed::Box>`, both of which have an undefined layout - --> $DIR/transmute_undefined_repr.rs:57:37 + --> $DIR/transmute_undefined_repr.rs:58:37 | LL | let _: Box> = transmute(value::<&'static mut Ty2>()); // Lint, different Ty2 instances | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: two instances of the same generic type (`Ty2`) may have different layouts -error: aborting due to 8 previous errors +error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:138:35 + | +LL | let _: Vec> = transmute(value::>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Vec`) may have different layouts + +error: transmute from `std::vec::Vec>` to `std::vec::Vec>`, both of which have an undefined layout + --> $DIR/transmute_undefined_repr.rs:139:35 + | +LL | let _: Vec> = transmute(value::>>()); // Err + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: two instances of the same generic type (`Vec`) may have different layouts + +error: aborting due to 10 previous errors diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.stderr b/tests/ui/transmutes_expressible_as_ptr_casts.stderr index d9b64a0ed..de9418c8d 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.stderr +++ b/tests/ui/transmutes_expressible_as_ptr_casts.stderr @@ -34,13 +34,13 @@ error: transmute from a reference to a pointer LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]` -error: transmute from `fn(usize) -> u8 {main::foo}` to `*const usize` which could be expressed as a pointer cast instead +error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead --> $DIR/transmutes_expressible_as_ptr_casts.rs:48:41 | LL | let _usize_ptr_transmute = unsafe { transmute:: u8, *const usize>(foo) }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize` -error: transmute from `fn(usize) -> u8 {main::foo}` to `usize` which could be expressed as a pointer cast instead +error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead --> $DIR/transmutes_expressible_as_ptr_casts.rs:52:49 | LL | let _usize_from_fn_ptr_transmute = unsafe { transmute:: u8, usize>(foo) }; diff --git a/tests/ui/unnecessary_join.fixed b/tests/ui/unnecessary_join.fixed new file mode 100644 index 000000000..7e12c6ae4 --- /dev/null +++ b/tests/ui/unnecessary_join.fixed @@ -0,0 +1,35 @@ +// run-rustfix + +#![warn(clippy::unnecessary_join)] + +fn main() { + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::(); + println!("{}", output); + + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::(); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join("\n"); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + println!("{}", output); +} diff --git a/tests/ui/unnecessary_join.rs b/tests/ui/unnecessary_join.rs new file mode 100644 index 000000000..0a21656a7 --- /dev/null +++ b/tests/ui/unnecessary_join.rs @@ -0,0 +1,37 @@ +// run-rustfix + +#![warn(clippy::unnecessary_join)] + +fn main() { + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join(""); + println!("{}", output); + + // should be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join(""); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector + .iter() + .map(|item| item.to_uppercase()) + .collect::>() + .join("\n"); + println!("{}", output); + + // should not be linted + let vector = vec!["hello", "world"]; + let output = vector.iter().map(|item| item.to_uppercase()).collect::(); + println!("{}", output); +} diff --git a/tests/ui/unnecessary_join.stderr b/tests/ui/unnecessary_join.stderr new file mode 100644 index 000000000..0b14b143a --- /dev/null +++ b/tests/ui/unnecessary_join.stderr @@ -0,0 +1,20 @@ +error: called `.collect>().join("")` on an iterator + --> $DIR/unnecessary_join.rs:11:10 + | +LL | .collect::>() + | __________^ +LL | | .join(""); + | |_________________^ help: try using: `collect::()` + | + = note: `-D clippy::unnecessary-join` implied by `-D warnings` + +error: called `.collect>().join("")` on an iterator + --> $DIR/unnecessary_join.rs:20:10 + | +LL | .collect::>() + | __________^ +LL | | .join(""); + | |_________________^ help: try using: `collect::()` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 4ba2a0a5d..65fcdc430 100644 --- a/tests/ui/unnecessary_lazy_eval.fixed +++ b/tests/ui/unnecessary_lazy_eval.fixed @@ -115,6 +115,14 @@ fn main() { let _: Result = res.or(Ok(2)); let _: Result = res.or(Ok(astronomers_pi)); let _: Result = res.or(Ok(ext_str.some_field)); + let _: Result = res. + // some lines + // some lines + // some lines + // some lines + // some lines + // some lines + or(Ok(ext_str.some_field)); // neither bind_instead_of_map nor unnecessary_lazy_eval applies here let _: Result = res.and_then(|x| Err(x)); diff --git a/tests/ui/unnecessary_lazy_eval.rs b/tests/ui/unnecessary_lazy_eval.rs index 466915217..206080ed6 100644 --- a/tests/ui/unnecessary_lazy_eval.rs +++ b/tests/ui/unnecessary_lazy_eval.rs @@ -115,6 +115,14 @@ fn main() { let _: Result = res.or_else(|_| Ok(2)); let _: Result = res.or_else(|_| Ok(astronomers_pi)); let _: Result = res.or_else(|_| Ok(ext_str.some_field)); + let _: Result = res. + // some lines + // some lines + // some lines + // some lines + // some lines + // some lines + or_else(|_| Ok(ext_str.some_field)); // neither bind_instead_of_map nor unnecessary_lazy_eval applies here let _: Result = res.and_then(|x| Err(x)); diff --git a/tests/ui/unnecessary_lazy_eval.stderr b/tests/ui/unnecessary_lazy_eval.stderr index cc94bd5cd..7e4dd7730 100644 --- a/tests/ui/unnecessary_lazy_eval.stderr +++ b/tests/ui/unnecessary_lazy_eval.stderr @@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:35:13 | LL | let _ = opt.unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(2)` + | ^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` | = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` @@ -10,187 +12,264 @@ error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:36:13 | LL | let _ = opt.unwrap_or_else(|| astronomers_pi); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)` + | ^^^^--------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:37:13 | LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)` + | ^^^^------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:39:13 | LL | let _ = opt.and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `opt.and(ext_opt)` + | ^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:40:13 | LL | let _ = opt.or_else(|| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(ext_opt)` + | ^^^^------------------- + | | + | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:41:13 | LL | let _ = opt.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(None)` + | ^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:42:13 | LL | let _ = opt.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `opt.get_or_insert(2)` + | ^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:43:13 | LL | let _ = opt.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `opt.ok_or(2)` + | ^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:44:13 | LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `nested_tuple_opt.unwrap_or(Some((1, 2)))` + | ^^^^^^^^^^^^^^^^^------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:47:13 | LL | let _ = Some(10).unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Some(10).unwrap_or(2)` + | ^^^^^^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:48:13 | LL | let _ = Some(10).and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `Some(10).and(ext_opt)` + | ^^^^^^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:49:28 | LL | let _: Option = None.or_else(|| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(ext_opt)` + | ^^^^^------------------- + | | + | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:50:13 | LL | let _ = None.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `None.get_or_insert(2)` + | ^^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:51:35 | LL | let _: Result = None.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `None.ok_or(2)` + | ^^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:52:28 | LL | let _: Option = None.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(None)` + | ^^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:55:13 | LL | let _ = deep.0.unwrap_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `deep.0.unwrap_or(2)` + | ^^^^^^^-------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:56:13 | LL | let _ = deep.0.and_then(|_| ext_opt); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `deep.0.and(ext_opt)` + | ^^^^^^^--------------------- + | | + | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:57:13 | LL | let _ = deep.0.or_else(|| None); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(None)` + | ^^^^^^^---------------- + | | + | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:58:13 | LL | let _ = deep.0.get_or_insert_with(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `deep.0.get_or_insert(2)` + | ^^^^^^^------------------------ + | | + | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:59:13 | LL | let _ = deep.0.ok_or_else(|| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `deep.0.ok_or(2)` + | ^^^^^^^---------------- + | | + | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:79:28 | LL | let _: Option = None.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(Some(3))` + | ^^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:80:13 | LL | let _ = deep.0.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(Some(3))` + | ^^^^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` --> $DIR/unnecessary_lazy_eval.rs:81:13 | LL | let _ = opt.or_else(|| Some(3)); - | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(Some(3))` + | ^^^^------------------- + | | + | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:87:13 | LL | let _ = res2.unwrap_or_else(|_| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(2)` + | ^^^^^--------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:88:13 | LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)` + | ^^^^^---------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:89:13 | LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)` + | ^^^^^-------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:111:35 | LL | let _: Result = res.and_then(|_| Err(2)); - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(2))` + | ^^^^-------------------- + | | + | help: use `and(..)` instead: `and(Err(2))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:112:35 | LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(astronomers_pi))` + | ^^^^--------------------------------- + | | + | help: use `and(..)` instead: `and(Err(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:113:35 | LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(ext_str.some_field))` + | ^^^^------------------------------------- + | | + | help: use `and(..)` instead: `and(Err(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:115:35 | LL | let _: Result = res.or_else(|_| Ok(2)); - | ^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(2))` + | ^^^^------------------ + | | + | help: use `or(..)` instead: `or(Ok(2))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:116:35 | LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(astronomers_pi))` + | ^^^^------------------------------- + | | + | help: use `or(..)` instead: `or(Ok(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval.rs:117:35 | LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(ext_str.some_field))` + | ^^^^----------------------------------- + | | + | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` -error: aborting due to 32 previous errors +error: unnecessary closure used to substitute value for `Result::Err` + --> $DIR/unnecessary_lazy_eval.rs:118:35 + | +LL | let _: Result = res. + | ___________________________________^ +LL | | // some lines +LL | | // some lines +LL | | // some lines +... | +LL | | // some lines +LL | | or_else(|_| Ok(ext_str.some_field)); + | |_________----------------------------------^ + | | + | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` + +error: aborting due to 33 previous errors diff --git a/tests/ui/unnecessary_lazy_eval_unfixable.stderr b/tests/ui/unnecessary_lazy_eval_unfixable.stderr index 75674b0a9..20acab6e8 100644 --- a/tests/ui/unnecessary_lazy_eval_unfixable.stderr +++ b/tests/ui/unnecessary_lazy_eval_unfixable.stderr @@ -2,7 +2,9 @@ error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:12:13 | LL | let _ = Ok(1).unwrap_or_else(|()| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^---------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` | = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` @@ -10,13 +12,17 @@ error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:16:13 | LL | let _ = Ok(1).unwrap_or_else(|e::E| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^------------------------ + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` --> $DIR/unnecessary_lazy_eval_unfixable.rs:17:13 | LL | let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)` + | ^^^^^^------------------------------------- + | | + | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: aborting due to 3 previous errors diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index 720138db1..38ba41ac5 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result(P); + + pub trait Abstracted {} + + impl

Abstracted for Opaque

{} + + fn build

(p: P) -> Opaque

+ where + P: AsRef, + { + Opaque(p) + } + + // Should not lint. + fn test_str(s: &str) -> Box { + Box::new(build(s.to_string())) + } + + // Should not lint. + fn test_x(x: super::X) -> Box { + Box::new(build(x)) + } + + #[derive(Clone, Copy)] + struct Y(&'static str); + + impl AsRef for Y { + fn as_ref(&self) -> &str { + self.0 + } + } + + impl ToString for Y { + fn to_string(&self) -> String { + self.0.to_string() + } + } + + // Should lint because Y is copy. + fn test_y(y: Y) -> Box { + Box::new(build(y)) + } +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index 60b2e718f..15fb7ee83 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -212,3 +212,51 @@ fn get_file_path(_file_type: &FileType) -> Result(P); + + pub trait Abstracted {} + + impl

Abstracted for Opaque

{} + + fn build

(p: P) -> Opaque

+ where + P: AsRef, + { + Opaque(p) + } + + // Should not lint. + fn test_str(s: &str) -> Box { + Box::new(build(s.to_string())) + } + + // Should not lint. + fn test_x(x: super::X) -> Box { + Box::new(build(x)) + } + + #[derive(Clone, Copy)] + struct Y(&'static str); + + impl AsRef for Y { + fn as_ref(&self) -> &str { + self.0 + } + } + + impl ToString for Y { + fn to_string(&self) -> String { + self.0.to_string() + } + } + + // Should lint because Y is copy. + fn test_y(y: Y) -> Box { + Box::new(build(y.to_string())) + } +} diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 1dfc65e22..c53ce32be 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -491,5 +491,11 @@ LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { | -error: aborting due to 76 previous errors +error: unnecessary use of `to_string` + --> $DIR/unnecessary_to_owned.rs:260:24 + | +LL | Box::new(build(y.to_string())) + | ^^^^^^^^^^^^^ help: use: `y` + +error: aborting due to 77 previous errors