From dc57fd7f5379e2e790761662430a1690e9770fdb Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Sat, 4 Apr 2026 14:39:31 +0200 Subject: [PATCH] Merge commit '88f787d193fb1f0491b001288e82b5574c080606' into clippy-subtree-update --- CHANGELOG.md | 2 + clippy_config/src/lib.rs | 1 - clippy_dev/src/lib.rs | 1 - clippy_dev/src/new_lint.rs | 4 +- clippy_lints/src/casts/unnecessary_cast.rs | 76 ++- clippy_lints/src/collapsible_if.rs | 20 +- clippy_lints/src/declared_lints.rs | 2 + clippy_lints/src/lib.rs | 4 +- .../src/loops/explicit_counter_loop.rs | 21 + clippy_lints/src/loops/manual_memcpy.rs | 4 +- clippy_lints/src/manual_is_ascii_check.rs | 11 +- clippy_lints/src/manual_noop_waker.rs | 71 +++ clippy_lints/src/manual_pop_if.rs | 235 +++++--- clippy_lints/src/matches/collapsible_match.rs | 52 +- clippy_lints/src/methods/filter_map.rs | 4 +- clippy_lints/src/methods/iter_kv_map.rs | 8 +- clippy_lints/src/methods/manual_option_zip.rs | 89 +++ clippy_lints/src/methods/mod.rs | 31 + clippy_lints/src/non_expressive_names.rs | 14 +- clippy_lints/src/repeat_vec_with_capacity.rs | 4 +- clippy_lints/src/string_patterns.rs | 4 +- clippy_lints/src/unit_types/unit_arg.rs | 16 +- .../derive_deserialize_allowing_unknown.rs | 168 ------ clippy_lints_internal/src/lib.rs | 3 - clippy_utils/README.md | 2 +- clippy_utils/src/lib.rs | 15 + clippy_utils/src/msrvs.rs | 2 +- clippy_utils/src/paths.rs | 2 + clippy_utils/src/sym.rs | 2 + lintcheck/Cargo.toml | 2 +- rust-toolchain.toml | 2 +- .../derive_deserialize_allowing_unknown.rs | 60 -- ...derive_deserialize_allowing_unknown.stderr | 23 - tests/ui/collapsible_if_unfixable.rs | 16 + tests/ui/collapsible_match.rs | 55 ++ tests/ui/explicit_counter_loop.rs | 31 + tests/ui/explicit_counter_loop.stderr | 26 +- tests/ui/iter_kv_map.fixed | 12 + tests/ui/iter_kv_map.rs | 12 + tests/ui/iter_kv_map.stderr | 20 +- tests/ui/manual_noop_waker.rs | 40 ++ tests/ui/manual_noop_waker.stderr | 20 + tests/ui/manual_option_zip.fixed | 118 ++++ tests/ui/manual_option_zip.rs | 118 ++++ tests/ui/manual_option_zip.stderr | 53 ++ tests/ui/manual_pop_if.fixed | 95 ++- tests/ui/manual_pop_if.rs | 135 +++-- tests/ui/manual_pop_if.stderr | 553 ++++++++++++++---- tests/ui/manual_pop_if_unfixable.rs | 128 ++++ tests/ui/manual_pop_if_unfixable.stderr | 193 ++++++ tests/ui/similar_names.rs | 12 +- tests/ui/similar_names.stderr | 30 +- tests/ui/unnecessary_cast.fixed | 8 + tests/ui/unnecessary_cast.rs | 8 + tests/ui/unnecessary_cast.stderr | 8 +- 55 files changed, 1978 insertions(+), 668 deletions(-) create mode 100644 clippy_lints/src/manual_noop_waker.rs create mode 100644 clippy_lints/src/methods/manual_option_zip.rs delete mode 100644 clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs delete mode 100644 tests/ui-internal/derive_deserialize_allowing_unknown.rs delete mode 100644 tests/ui-internal/derive_deserialize_allowing_unknown.stderr create mode 100644 tests/ui/manual_noop_waker.rs create mode 100644 tests/ui/manual_noop_waker.stderr create mode 100644 tests/ui/manual_option_zip.fixed create mode 100644 tests/ui/manual_option_zip.rs create mode 100644 tests/ui/manual_option_zip.stderr create mode 100644 tests/ui/manual_pop_if_unfixable.rs create mode 100644 tests/ui/manual_pop_if_unfixable.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 45bfb65b2e67..748e283edffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6797,9 +6797,11 @@ Released 2018-09-13 [`manual_midpoint`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_midpoint [`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive +[`manual_noop_waker`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_noop_waker [`manual_ok_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_err [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or [`manual_option_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice +[`manual_option_zip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_zip [`manual_pattern_char_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison [`manual_pop_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_pop_if [`manual_range_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index f18272ecf5a0..0a891e75eb3e 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -7,7 +7,6 @@ unused_qualifications )] #![allow(clippy::must_use_candidate, clippy::missing_panics_doc)] -#![deny(clippy::derive_deserialize_allowing_unknown)] extern crate rustc_data_structures; extern crate rustc_errors; diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 99709ed2e4f2..433d4e8cdcf1 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,7 +1,6 @@ #![feature( exit_status_error, new_range, - new_range_api, os_str_slice, os_string_truncate, pattern, diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 2abe471bed2b..dc2c6d8aa520 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -275,8 +275,8 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let _: fmt::Result = writedoc!( result, r" - use clippy_utils::msrvs::{{self, {msrv_ty}}}; use clippy_config::Conf; + use clippy_utils::msrvs::{{self, {msrv_ty}}}; {pass_import} use rustc_lint::{{{context_import}, {pass_type}}}; use rustc_session::impl_lint_pass; @@ -319,7 +319,7 @@ pub fn new(conf: &'static Conf) -> Self {{ impl {pass_type}{pass_lifetimes} for {name_camel} {{{extract_msrv}}} - // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed. + // TODO: Add MSRV level to `clippy_utils/src/msrvs.rs` if needed. // TODO: Update msrv config comment in `clippy_config/src/conf.rs` " ); diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 5cc41c121965..f822590a721d 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -1,7 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::numeric_literal::NumericLiteral; use clippy_utils::res::MaybeResPath as _; -use clippy_utils::source::{SpanRangeExt, snippet_opt}; +use clippy_utils::source::{SpanRangeExt, snippet, snippet_with_applicability}; +use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::{Visitable, for_each_expr_without_closures}; use clippy_utils::{get_parent_expr, is_hir_ty_cfg_dependant, is_ty_alias, sym}; use rustc_ast::{LitFloatType, LitIntType, LitKind}; @@ -24,7 +25,8 @@ pub(super) fn check<'tcx>( cast_from: Ty<'tcx>, cast_to: Ty<'tcx>, ) -> bool { - let cast_str = snippet_opt(cx, cast_expr.span).unwrap_or_default(); + let mut app = Applicability::MachineApplicable; + let cast_str = snippet_with_applicability(cx, cast_expr.span, "_", &mut app); if let ty::RawPtr(..) = cast_from.kind() // check both mutability and type are the same @@ -47,16 +49,23 @@ pub(super) fn check<'tcx>( _ => {}, } - span_lint_and_sugg( + // Preserve parentheses around `expr` in case of cascaded casts + let surrounding = + if matches!(cast_expr.kind, ExprKind::Cast(..)) && has_enclosing_paren(snippet(cx, expr.span, "")) { + MaybeParenOrBlock::Paren + } else { + MaybeParenOrBlock::Nothing + }; + + emit_lint( cx, - UNNECESSARY_CAST, - expr.span, + expr, format!( "casting raw pointers to the same type and constness is unnecessary (`{cast_from}` -> `{cast_to}`)" ), - "try", - cast_str.clone(), - Applicability::MaybeIncorrect, + &cast_str, + surrounding, + app.max(Applicability::MaybeIncorrect), ); } @@ -143,12 +152,6 @@ pub(super) fn check<'tcx>( } if cast_from.kind() == cast_to.kind() && !expr.span.in_external_macro(cx.sess().source_map()) { - enum MaybeParenOrBlock { - Paren, - Block, - Nothing, - } - fn is_borrow_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { matches!(expr.kind, ExprKind::AddrOf(..)) || cx @@ -188,18 +191,13 @@ fn is_in_allowed_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { _ => MaybeParenOrBlock::Nothing, }; - span_lint_and_sugg( + emit_lint( cx, - UNNECESSARY_CAST, - expr.span, + expr, format!("casting to the same type is unnecessary (`{cast_from}` -> `{cast_to}`)"), - "try", - match surrounding { - MaybeParenOrBlock::Paren => format!("({cast_str})"), - MaybeParenOrBlock::Block => format!("{{ {cast_str} }}"), - MaybeParenOrBlock::Nothing => cast_str, - }, - Applicability::MachineApplicable, + &cast_str, + surrounding, + app, ); return true; } @@ -312,3 +310,33 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx }) .is_some() } + +#[derive(Clone, Copy)] +enum MaybeParenOrBlock { + Paren, + Block, + Nothing, +} + +fn emit_lint( + cx: &LateContext<'_>, + expr: &Expr<'_>, + msg: String, + sugg: &str, + surrounding: MaybeParenOrBlock, + applicability: Applicability, +) { + span_lint_and_sugg( + cx, + UNNECESSARY_CAST, + expr.span, + msg, + "try", + match surrounding { + MaybeParenOrBlock::Paren => format!("({sugg})"), + MaybeParenOrBlock::Block => format!("{{ {sugg} }}"), + MaybeParenOrBlock::Nothing => sugg.to_string(), + }, + applicability, + ); +} diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 7f5bc520dc4d..ca04ce1764ff 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::source::{HasSession, IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; -use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text}; +use clippy_utils::{can_use_if_let_chains, span_contains_cfg, span_contains_non_whitespace, sym, tokenize_with_text}; use rustc_ast::BinOpKind; use rustc_errors::Applicability; use rustc_hir::attrs::{AttributeKind, LintAttributeKind}; @@ -170,6 +170,11 @@ fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: && self.eligible_condition(cx, check_inner) && expr.span.eq_ctxt(inner.span) && self.check_significant_tokens_and_expect_attrs(cx, then, inner, sym::collapsible_if) + && let then_closing_bracket = { + let end = then.span.shrink_to_hi(); + end.with_lo(end.lo() - BytePos(1)) + } + && !span_contains_cfg(cx, inner.span.between(then_closing_bracket)) { span_lint_hir_and_then( cx, @@ -179,12 +184,7 @@ fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: "this `if` statement can be collapsed", |diag| { let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span(); - let then_closing_bracket = { - let end = then.span.shrink_to_hi(); - end.with_lo(end.lo() - BytePos(1)) - .with_leading_whitespace(cx) - .into_span() - }; + let then_closing_bracket = then_closing_bracket.with_leading_whitespace(cx).into_span(); let (paren_start, inner_if_span, paren_end) = peel_parens(cx, inner.span); let inner_if = inner_if_span.split_at(2).0; let mut sugg = vec![ @@ -238,12 +238,10 @@ fn check_significant_tokens_and_expect_attrs( !span_contains_non_whitespace(cx, span, self.lint_commented_code) }, - [ - Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)), - ] => { + [Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs))] => { sub_attrs .into_iter() - .filter(|attr|attr.kind == LintAttributeKind::Expect) + .filter(|attr| attr.kind == LintAttributeKind::Expect) .flat_map(|attr| attr.lint_instances.iter().map(|group| (attr.attr_span, group))) .filter(|(_, lint_id)| { lint_id.tool_is_named(sym::clippy) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 441b907eaf2f..c164241673a3 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -311,6 +311,7 @@ crate::manual_let_else::MANUAL_LET_ELSE_INFO, crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO, + crate::manual_noop_waker::MANUAL_NOOP_WAKER_INFO, crate::manual_option_as_slice::MANUAL_OPTION_AS_SLICE_INFO, crate::manual_pop_if::MANUAL_POP_IF_INFO, crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO, @@ -418,6 +419,7 @@ crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_NEXT_BACK_INFO, crate::methods::MANUAL_OK_OR_INFO, + crate::methods::MANUAL_OPTION_ZIP_INFO, crate::methods::MANUAL_REPEAT_N_INFO, crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO, crate::methods::MANUAL_SPLIT_ONCE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 719484b30de8..68a8f51e7f4d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -209,6 +209,7 @@ mod manual_let_else; mod manual_main_separator_str; mod manual_non_exhaustive; +mod manual_noop_waker; mod manual_option_as_slice; mod manual_pop_if; mod manual_range_patterns; @@ -864,7 +865,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))), Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))), Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)), - Box::new(move |_| Box::new(manual_pop_if::ManualPopIf::new(conf))), + Box::new(move |tcx| Box::new(manual_pop_if::ManualPopIf::new(tcx, conf))), + Box::new(|_| Box::new(manual_noop_waker::ManualNoopWaker)), // add late passes here, used by `cargo dev new_lint` ]; store.late_passes.extend(late_lints); diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index 9bfc3aa56648..b813a18b221e 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -2,6 +2,7 @@ use super::{EXPLICIT_COUNTER_LOOP, IncrementVisitor, InitializeVisitor, make_iterator_snippet}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::higher::Range; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::{EMPTY, Sugg}; use clippy_utils::{get_enclosing_block, is_integer_const, is_integer_literal_untyped}; @@ -83,6 +84,26 @@ pub(super) fn check<'tcx>( snippet }); + // If the loop variable is unused and the range is `0..n`, suggest `(init..).take(n)`. + if pat_snippet == "_" + && let Some(range) = Range::hir(cx, arg) + && range.limits == RangeLimits::HalfOpen + && range.start.is_some_and(|start| is_integer_const(cx, start, 0)) + && let Some(end) = range.end + { + let end = snippet_with_applicability(cx, end.span, "..", &mut applicability); + diag.span_suggestion( + span, + "consider using", + format!( + "{loop_label}for {name} in ({}).take({end})", + initializer.range(&EMPTY, RangeLimits::HalfOpen) + ), + applicability, + ); + return; + } + diag.span_suggestion( span, "consider using", diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index e5c37377e857..3056be4e0d38 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -385,8 +385,8 @@ fn get_offset<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>, starts: &[Start<'tcx>] ExprKind::Binary(op, lhs, rhs) => match op.node { BinOpKind::Add => { let offset_opt = get_start(lhs, starts) - .and_then(|s| get_offset(cx, rhs, starts).map(|o| (s, o))) - .or_else(|| get_start(rhs, starts).and_then(|s| get_offset(cx, lhs, starts).map(|o| (s, o)))); + .zip(get_offset(cx, rhs, starts)) + .or_else(|| get_start(rhs, starts).zip(get_offset(cx, lhs, starts))); offset_opt.map(|(s, o)| (s, Offset::positive(o))) }, diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index b55b3995f732..dd1385e48e97 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -91,6 +91,13 @@ enum CharRange { impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !matches!( + expr.kind, + ExprKind::Match(_, [_, ..], _) | ExprKind::MethodCall(_, _, [_], _) + ) { + return; + } + if !self.msrv.meets(cx, msrvs::IS_ASCII_DIGIT) { return; } @@ -99,8 +106,8 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { return; } - let (arg, span, range) = if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) - && let ExprKind::Match(recv, [arm, ..], _) = expr.kind + let (arg, span, range) = if let ExprKind::Match(recv, [arm, ..], _) = expr.kind + && let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) { let recv = peel_ref_operators(cx, recv); let range = check_pat(&arm.pat.kind); diff --git a/clippy_lints/src/manual_noop_waker.rs b/clippy_lints/src/manual_noop_waker.rs new file mode 100644 index 000000000000..c5de39dbf7f9 --- /dev/null +++ b/clippy_lints/src/manual_noop_waker.rs @@ -0,0 +1,71 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::{is_empty_block, sym}; +use rustc_hir::{ImplItemKind, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for manual implementations of `std::task::Wake` that are empty. + /// + /// ### Why is this bad? + /// `Waker::noop()` provides a more performant and cleaner way to create a + /// waker that does nothing, avoiding unnecessary `Arc` allocations and + /// reference count increments. + /// + /// ### Example + /// ```rust + /// # use std::sync::Arc; + /// # use std::task::Wake; + /// struct MyWaker; + /// impl Wake for MyWaker { + /// fn wake(self: Arc) {} + /// fn wake_by_ref(self: &Arc) {} + /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// use std::task::Waker; + /// let waker = Waker::noop(); + /// ``` + #[clippy::version = "1.96.0"] + pub MANUAL_NOOP_WAKER, + complexity, + "manual implementations of noop wakers can be simplified using Waker::noop()" +} + +declare_lint_pass!(ManualNoopWaker => [MANUAL_NOOP_WAKER]); + +impl<'tcx> LateLintPass<'tcx> for ManualNoopWaker { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Impl(imp) = item.kind + && let Some(trait_ref) = imp.of_trait + && let Some(trait_id) = trait_ref.trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Wake, trait_id) + { + for impl_item_ref in imp.items { + let impl_item = cx + .tcx + .hir_node_by_def_id(impl_item_ref.owner_id.def_id) + .expect_impl_item(); + + if let ImplItemKind::Fn(_, body_id) = &impl_item.kind { + let body = cx.tcx.hir_body(*body_id); + if !is_empty_block(body.value) { + return; + } + } + } + + span_lint_and_help( + cx, + MANUAL_NOOP_WAKER, + trait_ref.trait_ref.path.span, + "manual implementation of a no-op waker", + None, + "use `std::task::Waker::noop()` instead", + ); + } + } +} diff --git a/clippy_lints/src/manual_pop_if.rs b/clippy_lints/src/manual_pop_if.rs index 6662a34bc332..bf53a8c27a6b 100644 --- a/clippy_lints/src/manual_pop_if.rs +++ b/clippy_lints/src/manual_pop_if.rs @@ -1,17 +1,19 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::visitors::is_local_used; -use clippy_utils::{eq_expr_value, is_else_clause, is_lang_item_or_ctor, peel_blocks_with_stmt, sym}; +use clippy_utils::visitors::{for_each_expr_without_closures, is_local_used}; +use clippy_utils::{eq_expr_value, is_else_clause, is_lang_item_or_ctor, span_contains_non_whitespace, sym}; use rustc_ast::LitKind; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, PatKind, RustcVersion, StmtKind}; +use rustc_errors::{Applicability, MultiSpan}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind, LangItem, PatKind, StmtKind, UnsafeSource}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol}; +use rustc_span::{BytePos, Span, Symbol}; use std::fmt; +use std::ops::ControlFlow; declare_clippy_lint! { /// ### What it does @@ -21,11 +23,9 @@ /// Using `pop_if` is more concise and idiomatic. /// /// ### Known issues - /// Currently, the lint does not handle the case where the - /// `if` condition is part of an `else if` branch. - /// - /// The lint also does not handle the case where - /// the popped value is assigned and used. + /// When the popped value is assigned or used in an expression, + /// or when the `if` condition is part of an `else if` branch, the + /// lint will trigger but will not provide an automatic suggestion. /// /// ### Examples /// ```no_run @@ -61,11 +61,24 @@ pub struct ManualPopIf { msrv: Msrv, + binary_heap_pop_if_feature_enabled: bool, } impl ManualPopIf { - pub fn new(conf: &'static Conf) -> Self { - Self { msrv: conf.msrv } + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + Self { + msrv: conf.msrv, + binary_heap_pop_if_feature_enabled: tcx.features().enabled(sym::binary_heap_pop_if), + } + } + + fn msrv_compatible(&self, cx: &LateContext<'_>, kind: ManualPopIfKind) -> bool { + match kind { + ManualPopIfKind::Vec => self.msrv.meets(cx, msrvs::VEC_POP_IF), + ManualPopIfKind::VecDequeBack => self.msrv.meets(cx, msrvs::VEC_DEQUE_POP_BACK_IF), + ManualPopIfKind::VecDequeFront => self.msrv.meets(cx, msrvs::VEC_DEQUE_POP_FRONT_IF), + ManualPopIfKind::BinaryHeap => self.binary_heap_pop_if_feature_enabled, + } } } @@ -75,20 +88,22 @@ enum ManualPopIfKind { Vec, VecDequeBack, VecDequeFront, + BinaryHeap, } impl ManualPopIfKind { - fn check_method(self) -> Symbol { + fn peek_method(self) -> Symbol { match self { ManualPopIfKind::Vec => sym::last, ManualPopIfKind::VecDequeBack => sym::back, ManualPopIfKind::VecDequeFront => sym::front, + ManualPopIfKind::BinaryHeap => sym::peek, } } fn pop_method(self) -> Symbol { match self { - ManualPopIfKind::Vec => sym::pop, + ManualPopIfKind::Vec | ManualPopIfKind::BinaryHeap => sym::pop, ManualPopIfKind::VecDequeBack => sym::pop_back, ManualPopIfKind::VecDequeFront => sym::pop_front, } @@ -96,7 +111,7 @@ fn pop_method(self) -> Symbol { fn pop_if_method(self) -> Symbol { match self { - ManualPopIfKind::Vec => sym::pop_if, + ManualPopIfKind::Vec | ManualPopIfKind::BinaryHeap => sym::pop_if, ManualPopIfKind::VecDequeBack => sym::pop_back_if, ManualPopIfKind::VecDequeFront => sym::pop_front_if, } @@ -107,14 +122,7 @@ fn is_diag_item(self, cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { match self { ManualPopIfKind::Vec => ty.is_diag_item(cx, sym::Vec), ManualPopIfKind::VecDequeBack | ManualPopIfKind::VecDequeFront => ty.is_diag_item(cx, sym::VecDeque), - } - } - - fn msrv(self) -> RustcVersion { - match self { - ManualPopIfKind::Vec => msrvs::VEC_POP_IF, - ManualPopIfKind::VecDequeBack => msrvs::VEC_DEQUE_POP_BACK_IF, - ManualPopIfKind::VecDequeFront => msrvs::VEC_DEQUE_POP_FRONT_IF, + ManualPopIfKind::BinaryHeap => ty.is_diag_item(cx, sym::BinaryHeap), } } } @@ -125,6 +133,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ManualPopIfKind::Vec => write!(f, "`Vec::pop_if`"), ManualPopIfKind::VecDequeBack => write!(f, "`VecDeque::pop_back_if`"), ManualPopIfKind::VecDequeFront => write!(f, "`VecDeque::pop_front_if`"), + ManualPopIfKind::BinaryHeap => write!(f, "`BinaryHeap::pop_if`"), } } } @@ -143,10 +152,18 @@ struct ManualPopIfPattern<'tcx> { /// Span of the if expression (including the `if` keyword) if_span: Span, + + /// Span of the: + /// - check call (`vec.last().is_some_and(|x| *x > 5)`) + /// - pop+unwrap call (`vec.pop().unwrap()`) + spans: MultiSpan, + + /// Whether we are able to provide a suggestion + suggestable: bool, } impl ManualPopIfPattern<'_> { - fn emit_lint(&self, cx: &LateContext<'_>) { + fn emit_lint(self, cx: &LateContext<'_>) { let mut app = Applicability::MachineApplicable; let ctxt = self.if_span.ctxt(); let collection_snippet = snippet_with_context(cx, self.collection_expr.span, ctxt, "..", &mut app).0; @@ -154,36 +171,23 @@ fn emit_lint(&self, cx: &LateContext<'_>) { let param_name = self.param_name; let pop_if_method = self.kind.pop_if_method(); - let suggestion = format!("{collection_snippet}.{pop_if_method}(|{param_name}| {predicate_snippet});"); - - span_lint_and_sugg( + span_lint_and_then( cx, MANUAL_POP_IF, - self.if_span, + self.spans, format!("manual implementation of {}", self.kind), - "try", - suggestion, - app, + |diag| { + let sugg = format!("{collection_snippet}.{pop_if_method}(|{param_name}| {predicate_snippet});"); + if self.suggestable { + diag.span_suggestion_verbose(self.if_span, "try", sugg, app); + } else { + diag.help(format!("try refactoring the code using `{sugg}`")); + } + }, ); } } -fn pop_value_is_used(then_block: &Expr<'_>) -> bool { - let ExprKind::Block(block, _) = then_block.kind else { - return true; - }; - - if block.expr.is_some() { - return true; - } - - match block.stmts { - [stmt] => !matches!(stmt.kind, StmtKind::Semi(_) | StmtKind::Item(_)), - [.., last] => matches!(last.kind, StmtKind::Expr(_)), - [] => false, - } -} - /// Checks for the pattern: /// ```ignore /// if vec.last().is_some_and(|x| *x > 5) { @@ -197,21 +201,17 @@ fn check_is_some_and_pattern<'tcx>( if_expr_span: Span, kind: ManualPopIfKind, ) -> Option> { - if pop_value_is_used(then_block) { - return None; - } - - let check_method = kind.check_method(); + let peek_method = kind.peek_method(); let pop_method = kind.pop_method(); if let ExprKind::MethodCall(path, receiver, [closure_arg], _) = cond.kind && path.ident.name == sym::is_some_and && let ExprKind::MethodCall(check_path, collection_expr, [], _) = receiver.kind - && check_path.ident.name == check_method + && check_path.ident.name == peek_method && kind.is_diag_item(cx, collection_expr) && let ExprKind::Closure(closure) = closure_arg.kind && let body = cx.tcx.hir_body(closure.body) - && let Some((pop_collection, _pop_span)) = check_pop_unwrap(then_block, pop_method) + && let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method) && eq_expr_value(cx, collection_expr, pop_collection) && let Some(param) = body.params.first() && let Some(ident) = param.pat.simple_ident() @@ -222,6 +222,8 @@ fn check_is_some_and_pattern<'tcx>( predicate: body.value, param_name: ident.name, if_span: if_expr_span, + spans: MultiSpan::from(vec![if_expr_span.with_hi(cond.span.hi()), pop_span]), + suggestable, }); } @@ -243,7 +245,7 @@ fn check_if_let_pattern<'tcx>( if_expr_span: Span, kind: ManualPopIfKind, ) -> Option> { - let check_method = kind.check_method(); + let peek_method = kind.peek_method(); let pop_method = kind.pop_method(); if let ExprKind::Let(let_expr) = cond.kind @@ -255,7 +257,7 @@ fn check_if_let_pattern<'tcx>( && is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) && let PatKind::Binding(_, binding_id, binding_name, _) = binding_pat.kind && let ExprKind::MethodCall(path, collection_expr, [], _) = let_expr.init.kind - && path.ident.name == check_method + && path.ident.name == peek_method && kind.is_diag_item(cx, collection_expr) && let ExprKind::Block(block, _) = then_block.kind { @@ -271,8 +273,7 @@ fn check_if_let_pattern<'tcx>( if let ExprKind::If(inner_cond, inner_then, None) = inner_if.kind && is_local_used(cx, inner_cond, binding_id) - && !pop_value_is_used(inner_then) - && let Some((pop_collection, _pop_span)) = check_pop_unwrap(inner_then, pop_method) + && let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, inner_then, pop_method) && eq_expr_value(cx, collection_expr, pop_collection) { return Some(ManualPopIfPattern { @@ -281,6 +282,12 @@ fn check_if_let_pattern<'tcx>( predicate: inner_cond, param_name: binding_name.name, if_span: if_expr_span, + spans: MultiSpan::from(vec![ + if_expr_span.with_hi(cond.span.hi()), + inner_if.span.with_hi(inner_cond.span.hi()), + pop_span, + ]), + suggestable, }); } } @@ -302,11 +309,7 @@ fn check_let_chain_pattern<'tcx>( if_expr_span: Span, kind: ManualPopIfKind, ) -> Option> { - if pop_value_is_used(then_block) { - return None; - } - - let check_method = kind.check_method(); + let peek_method = kind.peek_method(); let pop_method = kind.pop_method(); if let ExprKind::Binary(op, left, right) = cond.kind @@ -320,11 +323,10 @@ fn check_let_chain_pattern<'tcx>( && is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) && let PatKind::Binding(_, binding_id, binding_name, _) = binding_pat.kind && let ExprKind::MethodCall(path, collection_expr, [], _) = let_expr.init.kind - && path.ident.name == check_method + && path.ident.name == peek_method && kind.is_diag_item(cx, collection_expr) && is_local_used(cx, right, binding_id) - && !pop_value_is_used(then_block) - && let Some((pop_collection, _pop_span)) = check_pop_unwrap(then_block, pop_method) + && let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method) && eq_expr_value(cx, collection_expr, pop_collection) { return Some(ManualPopIfPattern { @@ -333,6 +335,8 @@ fn check_let_chain_pattern<'tcx>( predicate: right, param_name: binding_name.name, if_span: if_expr_span, + spans: MultiSpan::from(vec![if_expr_span.with_hi(cond.span.hi()), pop_span]), + suggestable, }); } } @@ -353,11 +357,7 @@ fn check_map_unwrap_or_pattern<'tcx>( if_expr_span: Span, kind: ManualPopIfKind, ) -> Option> { - if pop_value_is_used(then_block) { - return None; - } - - let check_method = kind.check_method(); + let peek_method = kind.peek_method(); let pop_method = kind.pop_method(); if let ExprKind::MethodCall(unwrap_path, receiver, [default_arg], _) = cond.kind @@ -366,12 +366,12 @@ fn check_map_unwrap_or_pattern<'tcx>( && let ExprKind::MethodCall(map_path, map_receiver, [closure_arg], _) = receiver.kind && map_path.ident.name == sym::map && let ExprKind::MethodCall(check_path, collection_expr, [], _) = map_receiver.kind - && check_path.ident.name == check_method + && check_path.ident.name == peek_method && kind.is_diag_item(cx, collection_expr) && let ExprKind::Closure(closure) = closure_arg.kind && let body = cx.tcx.hir_body(closure.body) && cx.typeck_results().expr_ty(body.value).is_bool() - && let Some((pop_collection, _pop_span)) = check_pop_unwrap(then_block, pop_method) + && let Some((pop_collection, pop_span, suggestable)) = check_pop_unwrap(cx, then_block, pop_method) && eq_expr_value(cx, collection_expr, pop_collection) && let Some(param) = body.params.first() && let Some(ident) = param.pat.simple_ident() @@ -382,6 +382,8 @@ fn check_map_unwrap_or_pattern<'tcx>( predicate: body.value, param_name: ident.name, if_span: if_expr_span, + spans: MultiSpan::from(vec![if_expr_span.with_hi(cond.span.hi()), pop_span]), + suggestable, }); } @@ -389,19 +391,72 @@ fn check_map_unwrap_or_pattern<'tcx>( } /// Checks for `collection.().unwrap()` or `collection.().expect(..)` -/// and returns the collection and the span of the peeled expr -fn check_pop_unwrap<'tcx>(expr: &'tcx Expr<'_>, pop_method: Symbol) -> Option<(&'tcx Expr<'tcx>, Span)> { - let inner_expr = peel_blocks_with_stmt(expr); +/// and returns the collection expression and the span of the pop+unwrap call. +/// If the pop+unwrap is the only statement in the block, the result is marked as +/// suggestable (we can provide an automatic fix). +fn check_pop_unwrap<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + pop_method: Symbol, +) -> Option<(&'tcx Expr<'tcx>, Span, bool)> { + let ExprKind::Block(block, _) = expr.kind else { + return None; + }; - if let ExprKind::MethodCall(unwrap_path, receiver, _, _) = inner_expr.kind - && matches!(unwrap_path.ident.name, sym::unwrap | sym::expect) - && let ExprKind::MethodCall(pop_path, collection_expr, [], _) = receiver.kind - && pop_path.ident.name == pop_method + let as_pop_unwrap = |expr: &Expr<'tcx>| -> Option<(&'tcx Expr<'tcx>, Span)> { + if let ExprKind::MethodCall(unwrap_path, receiver, _, _) = expr.kind + && matches!( + unwrap_path.ident.name, + sym::unwrap | sym::unwrap_unchecked | sym::expect + ) + && let ExprKind::MethodCall(pop_path, collection_expr, [], _) = receiver.kind + && pop_path.ident.name == pop_method + { + Some((collection_expr, expr.span)) + } else { + None + } + }; + + // Peel through an `unsafe` block for `unwrap_unchecked`. + let peel_unsafe = |expr: &'tcx Expr<'tcx>| -> &'tcx Expr<'tcx> { + if let ExprKind::Block(block, _) = expr.kind + && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + && block.stmts.is_empty() + && let Some(inner) = block.expr + { + inner + } else { + expr + } + }; + + // Check for single statement with the pop unwrap (not in a macro or other expression) + // and that there are no comments or other text before or after the pop call. + if let [stmt] = block.stmts + && block.expr.is_none() + && let StmtKind::Semi(stmt_expr) | StmtKind::Expr(stmt_expr) = &stmt.kind + && !stmt_expr.span.from_expansion() + && let Some((collection_expr, span)) = as_pop_unwrap(peel_unsafe(stmt_expr)) { - return Some((collection_expr, inner_expr.span)); + let span_before = block + .span + .with_lo(block.span.lo() + BytePos(1)) + .with_hi(stmt_expr.span.lo()); + let span_after = stmt.span.shrink_to_hi().with_hi(block.span.hi() - BytePos(1)); + let suggestable = !span_contains_non_whitespace(cx, span_before, false) + && !span_contains_non_whitespace(cx, span_after, false); + return Some((collection_expr, span, suggestable)); } - None + // Check if the pop unwrap is present at all + for_each_expr_without_closures(block, |expr| { + if let Some((collection_expr, span)) = as_pop_unwrap(expr) { + ControlFlow::Break((collection_expr, span, false)) + } else { + ControlFlow::Continue(()) + } + }) } impl<'tcx> LateLintPass<'tcx> for ManualPopIf { @@ -410,22 +465,24 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { return; }; - // Do not lint if we are in an else-if branch. - if is_else_clause(cx.tcx, expr) { - return; - } + let in_else_clause = is_else_clause(cx.tcx, expr); for kind in [ ManualPopIfKind::Vec, ManualPopIfKind::VecDequeBack, ManualPopIfKind::VecDequeFront, + ManualPopIfKind::BinaryHeap, ] { - if let Some(pattern) = check_is_some_and_pattern(cx, cond, then_block, expr.span, kind) + if let Some(mut pattern) = check_is_some_and_pattern(cx, cond, then_block, expr.span, kind) .or_else(|| check_if_let_pattern(cx, cond, then_block, expr.span, kind)) .or_else(|| check_let_chain_pattern(cx, cond, then_block, expr.span, kind)) .or_else(|| check_map_unwrap_or_pattern(cx, cond, then_block, expr.span, kind)) - && self.msrv.meets(cx, kind.msrv()) + && self.msrv_compatible(cx, kind) { + if in_else_clause { + pattern.suggestable = false; + } + pattern.emit_lint(cx); return; } diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index de5f83b4745f..cb784d1ff660 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -3,13 +3,17 @@ use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{IntoSpan, SpanRangeExt, snippet}; +use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::is_local_used; use clippy_utils::{SpanlessEq, get_ref_operators, is_unit_expr, peel_blocks_with_stmt, peel_ref_operators}; use rustc_ast::BorrowKind; use rustc_errors::{Applicability, MultiSpan}; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdSet, Pat, PatExpr, PatExprKind, PatKind}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::LateContext; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; use rustc_span::symbol::Ident; use rustc_span::{BytePos, Span}; @@ -129,6 +133,7 @@ fn check_arm<'tcx>( (None, Some(e)) | (Some(e), None) => is_unit_expr(e), (Some(a), Some(b)) => SpanlessEq::new(cx).eq_expr(a, b), } + && !pat_bindings_moved_or_mutated(cx, outer_pat, inner.cond) { span_lint_hir_and_then( cx, @@ -255,3 +260,48 @@ fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option { Some(req_method_calls) } + +/// Checks if any of the bindings in the `pat` are moved or mutated in the `expr`. It is invalid to +/// move or mutate bindings in `if` guards. +fn pat_bindings_moved_or_mutated<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + let mut delegate = MovedVarDelegate { + moved: HirIdSet::default(), + }; + if ExprUseVisitor::for_clippy(cx, expr.hir_id.owner.def_id, &mut delegate) + .walk_expr(expr) + .is_err() + { + return true; + } + + let mut candidates = delegate.moved; + if let Some(mutated) = mutated_variables(expr, cx) { + candidates.extend(mutated); + } + + !pat.walk_short(|pat| { + if let PatKind::Binding(_, hir_id, ..) = pat.kind + && candidates.contains(&hir_id) + { + return false; + } + true + }) +} + +struct MovedVarDelegate { + moved: HirIdSet, +} + +impl<'tcx> Delegate<'tcx> for MovedVarDelegate { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(hir_id) = cmt.place.base { + self.moved.insert(hir_id); + } + } + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index d2e593fc17df..f4b4eed26090 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -247,11 +247,11 @@ fn hir(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, filter_param_id: HirId) - }), _ => None, } - } else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() + } else if let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind // we know for a fact that the wildcard pattern is the second arm - && let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind && scrutinee.res_local_id() == Some(filter_param_id) && let PatKind::TupleStruct(QPath::Resolved(_, path), ..) = arm.pat.kind + && matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() && let Some(variant_def_id) = path.res.opt_def_id() { Some(OffendingFilterExpr::Matches { variant_def_id }) diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index 79034fa23300..283b9b5fc5b4 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -48,14 +48,20 @@ pub(super) fn check<'tcx>( if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind && let [local_ident] = path.segments && local_ident.ident.name == bound_ident.name + && [sym::map, sym::flat_map].contains(&method_name) { + let identity_map_equivalent = match method_name { + sym::map => "", + sym::flat_map => ".flatten()", + _ => unreachable!(), + }; span_lint_and_sugg( cx, ITER_KV_MAP, expr.span, format!("iterating on a map's {replacement_kind}s"), "try", - format!("{recv_snippet}.{into_prefix}{replacement_kind}s()"), + format!("{recv_snippet}.{into_prefix}{replacement_kind}s(){identity_map_equivalent}"), applicability, ); } else { diff --git a/clippy_lints/src/methods/manual_option_zip.rs b/clippy_lints/src/methods/manual_option_zip.rs new file mode 100644 index 000000000000..203957c15658 --- /dev/null +++ b/clippy_lints/src/methods/manual_option_zip.rs @@ -0,0 +1,89 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::peel_blocks; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::visitors::for_each_expr_without_closures; +use rustc_errors::Applicability; +use rustc_hir::{self as hir, Expr, ExprKind, HirId, PatKind}; +use rustc_lint::LateContext; +use rustc_span::symbol::sym; +use std::ops::ControlFlow; + +use super::MANUAL_OPTION_ZIP; + +/// Checks for `a.and_then(|a| b.map(|b| (a, b)))` and suggests `a.zip(b)`. +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, + msrv: Msrv, +) { + // Looking for: `a.and_then(|a| b.map(|b| (a, b)))`. + // `and_then(|a| ...)` + if let ExprKind::Closure(&hir::Closure { body: outer_body_id, .. }) = arg.kind + && let hir::Body { params: [outer_param], value: outer_value, .. } = cx.tcx.hir_body(outer_body_id) + && let PatKind::Binding(_, outer_param_id, _, None) = outer_param.pat.kind + && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) + // `b.map(|b| ...)` + && let ExprKind::MethodCall(method_path, map_recv, [map_arg], _) = peel_blocks(outer_value).kind + && method_path.ident.name == sym::map + && cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option) + // `b` does not reference the outer closure parameter `a`. + && for_each_expr_without_closures(map_recv, |e| { + if e.res_local_id() == Some(outer_param_id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }).is_none() + // `|b| (a, b)` + && let ExprKind::Closure(&hir::Closure { body: inner_body_id, .. }) = map_arg.kind + && let hir::Body { params: [inner_param], value: inner_value, .. } = cx.tcx.hir_body(inner_body_id) + && let PatKind::Binding(_, inner_param_id, _, None) = inner_param.pat.kind + // `(a, b)` or `(b, a)` — tuple of outer and inner param in either order. + && let ExprKind::Tup([first, second]) = peel_blocks(inner_value).kind + && let Some((zip_recv, zip_arg)) = zip_operands(first, second, outer_param_id, inner_param_id, recv, map_recv) + // `Option.zip()` is available. + && msrv.meets(cx, msrvs::OPTION_ZIP) + { + let mut applicability = Applicability::MachineApplicable; + let zip_recv_snip = snippet_with_applicability(cx, zip_recv.span, "_", &mut applicability); + let zip_arg_snip = snippet_with_applicability(cx, zip_arg.span, "_", &mut applicability); + let suggestion = format!("{zip_recv_snip}.zip({zip_arg_snip})"); + + span_lint_and_sugg( + cx, + MANUAL_OPTION_ZIP, + expr.span, + "manual implementation of `Option::zip`", + "use", + suggestion, + applicability, + ); + } +} + +/// Given the two tuple elements and the `and_then` receiver / `map` receiver, returns the +/// `(zip_receiver, zip_argument)` expressions for the `.zip()` suggestion. +/// +/// For `(outer, inner)` order the zip is `recv.zip(map_recv)`. +/// For `(inner, outer)` (reversed) the zip is `map_recv.zip(recv)`. +/// Returns `None` if the tuple elements don't match either order. +fn zip_operands<'a>( + first: &Expr<'_>, + second: &Expr<'_>, + outer_param_id: HirId, + inner_param_id: HirId, + recv: &'a Expr<'a>, + map_recv: &'a Expr<'a>, +) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> { + if first.res_local_id() == Some(outer_param_id) && second.res_local_id() == Some(inner_param_id) { + Some((recv, map_recv)) + } else if first.res_local_id() == Some(inner_param_id) && second.res_local_id() == Some(outer_param_id) { + Some((map_recv, recv)) + } else { + None + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b647dbdc8468..b39aec6e521c 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -63,6 +63,7 @@ mod manual_is_variant_and; mod manual_next_back; mod manual_ok_or; +mod manual_option_zip; mod manual_repeat_n; mod manual_saturating_arithmetic; mod manual_str_repeat; @@ -1950,6 +1951,34 @@ "finds patterns that can be encoded more concisely with `Option::ok_or`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `a.and_then(|a| b.map(|b| (a, b)))` which can be + /// more concisely expressed as `a.zip(b)`. + /// + /// ### Why is this bad? + /// `Option::zip` is more concise and directly expresses the intent of + /// combining two `Option` values into a tuple. + /// + /// ### Example + /// ```no_run + /// let a: Option = Some(1); + /// let b: Option = Some(2); + /// let _ = a.and_then(|x| b.map(|y| (x, y))); + /// ``` + /// + /// Use instead: + /// ```no_run + /// let a: Option = Some(1); + /// let b: Option = Some(2); + /// let _ = a.zip(b); + /// ``` + #[clippy::version = "1.95.0"] + pub MANUAL_OPTION_ZIP, + complexity, + "manual reimplementation of `Option::zip`" +} + declare_clippy_lint! { /// ### What it does /// @@ -4814,6 +4843,7 @@ MANUAL_IS_VARIANT_AND, MANUAL_NEXT_BACK, MANUAL_OK_OR, + MANUAL_OPTION_ZIP, MANUAL_REPEAT_N, MANUAL_SATURATING_ARITHMETIC, MANUAL_SPLIT_ONCE, @@ -5115,6 +5145,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { } }, (sym::and_then, [arg]) => { + manual_option_zip::check(cx, expr, recv, arg, self.msrv); let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg); let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg); if !biom_option_linted && !biom_result_linted { diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 72542d4845b3..21ddefb249ca 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -52,7 +52,11 @@ declare_clippy_lint! { /// ### What it does - /// Checks for names that are very similar and thus confusing. + /// Checks for names that are very similar and thus confusing. In particular, + /// the lint checks for names with a single character change. + /// + /// It does not warn about names that have a single additional character at + /// the beginning nor the end; only insertions in the middle are considered. /// /// Note: this lint looks for similar names throughout each /// scope. To allow it, you need to allow it on the scope @@ -65,7 +69,13 @@ /// ### Example /// ```ignore /// let checked_exp = something; - /// let checked_expr = something_else; + /// let checked_eap = something_else; + /// ``` + /// + /// ### Example 2 + /// ```ignore + /// let orange = val; + /// let ornange = val2; /// ``` #[clippy::version = "pre 1.29.0"] pub SIMILAR_NAMES, diff --git a/clippy_lints/src/repeat_vec_with_capacity.rs b/clippy_lints/src/repeat_vec_with_capacity.rs index fdb459364efe..637d9bf7af28 100644 --- a/clippy_lints/src/repeat_vec_with_capacity.rs +++ b/clippy_lints/src/repeat_vec_with_capacity.rs @@ -77,8 +77,8 @@ fn emit_lint(cx: &LateContext<'_>, span: Span, kind: &str, note: &'static str, s /// Checks `vec![Vec::with_capacity(x); n]` fn check_vec_macro(cx: &LateContext<'_>, expr: &Expr<'_>) { - if matching_root_macro_call(cx, expr.span, sym::vec_macro).is_some() - && let Some(VecArgs::Repeat(repeat_expr, len_expr)) = VecArgs::hir(cx, expr) + if let Some(VecArgs::Repeat(repeat_expr, len_expr)) = VecArgs::hir(cx, expr) + && matching_root_macro_call(cx, expr.span, sym::vec_macro).is_some() && fn_def_id(cx, repeat_expr).is_some_and(|did| cx.tcx.is_diagnostic_item(sym::vec_with_capacity, did)) && !len_expr.span.from_expansion() && let Some(Constant::Int(2..)) = ConstEvalCtxt::new(cx).eval(expr_or_init(cx, len_expr)) diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs index 3528fa36e2d1..b4eb8977bf0f 100644 --- a/clippy_lints/src/string_patterns.rs +++ b/clippy_lints/src/string_patterns.rs @@ -166,9 +166,9 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr< }, ExprKind::Binary(op, _, _) if op.node == BinOpKind::Or => ControlFlow::Continue(Descend::Yes), ExprKind::Match(match_value, [arm, _], _) => { - if matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none() - || arm.guard.is_some() + if arm.guard.is_some() || match_value.res_local_id() != Some(binding) + || matching_root_macro_call(cx, sub_expr.span, sym::matches_macro).is_none() { return ControlFlow::Break(()); } diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index ae6d8a1c1aa3..973b2e95b474 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -4,7 +4,7 @@ use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::expr_type_is_certain; -use clippy_utils::{is_expr_default, is_from_proc_macro}; +use clippy_utils::{is_empty_block, is_expr_default, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind}; use rustc_lint::LateContext; @@ -205,20 +205,6 @@ fn is_block_with_no_expr(expr: &Expr<'_>) -> bool { matches!(expr.kind, ExprKind::Block(Block { expr: None, .. }, _)) } -fn is_empty_block(expr: &Expr<'_>) -> bool { - matches!( - expr.kind, - ExprKind::Block( - Block { - stmts: [], - expr: None, - .. - }, - _, - ) - ) -} - fn fmt_stmts_and_call( cx: &LateContext<'_>, call_expr: &Expr<'_>, diff --git a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs b/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs deleted file mode 100644 index 7190b8bf39b8..000000000000 --- a/clippy_lints_internal/src/derive_deserialize_allowing_unknown.rs +++ /dev/null @@ -1,168 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use clippy_utils::paths; -use rustc_ast::tokenstream::{TokenStream, TokenTree}; -use rustc_ast::{AttrStyle, DelimArgs}; -use rustc_hir::def::Res; -use rustc_hir::def_id::LocalDefId; -use rustc_hir::{ - AttrArgs, AttrItem, AttrPath, Attribute, HirId, Impl, Item, ItemKind, Path, QPath, TraitImplHeader, TraitRef, Ty, - TyKind, find_attr, -}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_lint_defs::declare_tool_lint; -use rustc_middle::ty::TyCtxt; -use rustc_session::declare_lint_pass; - -declare_tool_lint! { - /// ### What it does - /// Checks for structs or enums that derive `serde::Deserialize` and that - /// do not have a `#[serde(deny_unknown_fields)]` attribute. - /// - /// ### Why is this bad? - /// If the struct or enum is used in [`clippy_config::conf::Conf`] and a - /// user inserts an unknown field by mistake, the user's error will be - /// silently ignored. - /// - /// ### Example - /// ```rust - /// #[derive(serde::Deserialize)] - /// pub struct DisallowedPath { - /// path: String, - /// reason: Option, - /// replacement: Option, - /// } - /// ``` - /// - /// Use instead: - /// ```rust - /// #[derive(serde::Deserialize)] - /// #[serde(deny_unknown_fields)] - /// pub struct DisallowedPath { - /// path: String, - /// reason: Option, - /// replacement: Option, - /// } - /// ``` - pub clippy::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, - Allow, - "`#[derive(serde::Deserialize)]` without `#[serde(deny_unknown_fields)]`", - report_in_external_macro: true -} - -declare_lint_pass!(DeriveDeserializeAllowingUnknown => [DERIVE_DESERIALIZE_ALLOWING_UNKNOWN]); - -impl<'tcx> LateLintPass<'tcx> for DeriveDeserializeAllowingUnknown { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - // Is this an `impl` (of a certain form)? - let ItemKind::Impl(Impl { - of_trait: - Some(TraitImplHeader { - trait_ref: - TraitRef { - path: - Path { - res: Res::Def(_, trait_def_id), - .. - }, - .. - }, - .. - }), - self_ty: - Ty { - kind: - TyKind::Path(QPath::Resolved( - None, - Path { - res: Res::Def(_, self_ty_def_id), - .. - }, - )), - .. - }, - .. - }) = item.kind - else { - return; - }; - - // Is it an `impl` of the trait `serde::Deserialize`? - if !paths::SERDE_DESERIALIZE.get(cx).contains(trait_def_id) { - return; - } - - // Is it derived? - if !find_attr!(cx.tcx.hir_attrs(item.hir_id()), AutomaticallyDerived(..)) { - return; - } - - // Is `self_ty` local? - let Some(local_def_id) = self_ty_def_id.as_local() else { - return; - }; - - // Does `self_ty` have a variant with named fields? - if !has_variant_with_named_fields(cx.tcx, local_def_id) { - return; - } - - let hir_id = cx.tcx.local_def_id_to_hir_id(local_def_id); - - // Does `self_ty` have `#[serde(deny_unknown_fields)]`? - if let Some(tokens) = find_serde_attr_item(cx.tcx, hir_id) - && tokens.iter().any(is_deny_unknown_fields_token) - { - return; - } - - span_lint( - cx, - DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, - item.span, - "`#[derive(serde::Deserialize)]` without `#[serde(deny_unknown_fields)]`", - ); - } -} - -// Determines whether `def_id` corresponds to an ADT with at least one variant with named fields. A -// variant has named fields if its `ctor` field is `None`. -fn has_variant_with_named_fields(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { - let ty = tcx.type_of(def_id).skip_binder(); - - let rustc_middle::ty::Adt(adt_def, _) = ty.kind() else { - return false; - }; - - adt_def.variants().iter().any(|variant_def| variant_def.ctor.is_none()) -} - -fn find_serde_attr_item(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<&TokenStream> { - tcx.hir_attrs(hir_id).iter().find_map(|attribute| { - if let Attribute::Unparsed(attr_item) = attribute - && let AttrItem { - path: AttrPath { segments, .. }, - args: AttrArgs::Delimited(DelimArgs { tokens, .. }), - style: AttrStyle::Outer, - .. - } = &**attr_item - && segments.len() == 1 - && segments[0].as_str() == "serde" - { - Some(tokens) - } else { - None - } - }) -} - -fn is_deny_unknown_fields_token(tt: &TokenTree) -> bool { - if let TokenTree::Token(token, _) = tt - && token - .ident() - .is_some_and(|(token, _)| token.as_str() == "deny_unknown_fields") - { - true - } else { - false - } -} diff --git a/clippy_lints_internal/src/lib.rs b/clippy_lints_internal/src/lib.rs index 502d5cd3f340..8e2166858fec 100644 --- a/clippy_lints_internal/src/lib.rs +++ b/clippy_lints_internal/src/lib.rs @@ -30,7 +30,6 @@ mod almost_standard_lint_formulation; mod collapsible_span_lint_calls; -mod derive_deserialize_allowing_unknown; mod internal_paths; mod lint_without_lint_pass; mod msrv_attr_impl; @@ -47,7 +46,6 @@ static LINTS: &[&Lint] = &[ almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION, collapsible_span_lint_calls::COLLAPSIBLE_SPAN_LINT_CALLS, - derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN, lint_without_lint_pass::DEFAULT_LINT, lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE, lint_without_lint_pass::LINT_WITHOUT_LINT_PASS, @@ -68,7 +66,6 @@ pub fn register_lints(store: &mut LintStore) { store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths)); store.register_early_pass(|| Box::new(produce_ice::ProduceIce)); store.register_late_pass(|_| Box::new(collapsible_span_lint_calls::CollapsibleCalls)); - store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::::default()); store.register_late_pass(|_| Box::new(unnecessary_def_path::UnnecessaryDefPath)); diff --git a/clippy_utils/README.md b/clippy_utils/README.md index b5e2e0fbf6c5..683ca090e92a 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2026-03-21 +nightly-2026-04-02 ``` diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 8f028ba0878c..a8ff9b4cf6fb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -311,6 +311,21 @@ pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Optio } } +/// Check if the given `Expr` is an empty block (i.e. `{}`) or not. +pub fn is_empty_block(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block( + Block { + stmts: [], + expr: None, + .. + }, + _, + ) + ) +} + /// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index ecadc3322e14..4f9a064bf7a6 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -60,7 +60,7 @@ macro_rules! msrv_aliases { 1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS } 1,50,0 { BOOL_THEN, CLAMP, SLICE_FILL } 1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST } - 1,46,0 { CONST_IF_MATCH } + 1,46,0 { CONST_IF_MATCH, OPTION_ZIP } 1,45,0 { STR_STRIP_PREFIX } 1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS } 1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 3bd98ef24038..c5fd66eeb93c 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -133,6 +133,8 @@ macro_rules! $name { macro_path: PathNS::Macro, } +// Paths in the standard library missing a diagnostic item + // Paths in external crates pub static FUTURES_IO_ASYNCREADEXT: PathLookup = type_path!(futures_util::AsyncReadExt); pub static FUTURES_IO_ASYNCWRITEEXT: PathLookup = type_path!(futures_util::AsyncWriteExt); diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index 7d579d85d808..03d81e010f7b 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -122,6 +122,7 @@ macro_rules! generate { V6, VecDeque, Visitor, + Wake, Waker, Weak, Wrapping, @@ -141,6 +142,7 @@ macro_rules! generate { assert_failed, author, back, + binary_heap_pop_if, binaryheap_iter, bool_then, borrow, diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 0d0b80c309dd..34281f9f721b 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -21,7 +21,7 @@ rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" strip-ansi-escapes = "0.2.0" -tar = "0.4" +tar = "0.4.45" toml = "0.9.7" ureq = { version = "2.2", features = ["json"] } walkdir = "2.3" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 1f8bcdea8251..22b548848805 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2026-03-21" +channel = "nightly-2026-04-02" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui-internal/derive_deserialize_allowing_unknown.rs b/tests/ui-internal/derive_deserialize_allowing_unknown.rs deleted file mode 100644 index 9dc8e9e8f4c1..000000000000 --- a/tests/ui-internal/derive_deserialize_allowing_unknown.rs +++ /dev/null @@ -1,60 +0,0 @@ -#![deny(clippy::derive_deserialize_allowing_unknown)] - -use serde::{Deserialize, Deserializer}; - -#[derive(Deserialize)] //~ derive_deserialize_allowing_unknown -struct Struct { - flag: bool, - limit: u64, -} - -#[derive(Deserialize)] //~ derive_deserialize_allowing_unknown -enum Enum { - A(bool), - B { limit: u64 }, -} - -// negative tests - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -struct StructWithDenyUnknownFields { - flag: bool, - limit: u64, -} - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -enum EnumWithDenyUnknownFields { - A(bool), - B { limit: u64 }, -} - -#[derive(Deserialize)] -#[serde(untagged, deny_unknown_fields)] -enum MultipleSerdeAttributes { - A(bool), - B { limit: u64 }, -} - -#[derive(Deserialize)] -struct TupleStruct(u64, bool); - -#[derive(Deserialize)] -#[serde(deny_unknown_fields)] -enum EnumWithOnlyTupleVariants { - A(bool), - B(u64), -} - -struct ManualSerdeImplementation; - -impl<'de> Deserialize<'de> for ManualSerdeImplementation { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let () = <() as Deserialize>::deserialize(deserializer)?; - Ok(ManualSerdeImplementation) - } -} diff --git a/tests/ui-internal/derive_deserialize_allowing_unknown.stderr b/tests/ui-internal/derive_deserialize_allowing_unknown.stderr deleted file mode 100644 index 93d64826c993..000000000000 --- a/tests/ui-internal/derive_deserialize_allowing_unknown.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: `#[derive(serde::Deserialize)]` without `#[serde(deny_unknown_fields)]` - --> tests/ui-internal/derive_deserialize_allowing_unknown.rs:5:10 - | -LL | #[derive(Deserialize)] - | ^^^^^^^^^^^ - | -note: the lint level is defined here - --> tests/ui-internal/derive_deserialize_allowing_unknown.rs:1:9 - | -LL | #![deny(clippy::derive_deserialize_allowing_unknown)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: `#[derive(serde::Deserialize)]` without `#[serde(deny_unknown_fields)]` - --> tests/ui-internal/derive_deserialize_allowing_unknown.rs:11:10 - | -LL | #[derive(Deserialize)] - | ^^^^^^^^^^^ - | - = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: aborting due to 2 previous errors - diff --git a/tests/ui/collapsible_if_unfixable.rs b/tests/ui/collapsible_if_unfixable.rs index 643520ac0f5d..9537b7474223 100644 --- a/tests/ui/collapsible_if_unfixable.rs +++ b/tests/ui/collapsible_if_unfixable.rs @@ -18,3 +18,19 @@ fn issue13365() { } //~^^^^ ERROR: this lint expectation is unfulfilled } + +#[allow(unexpected_cfgs)] +fn issue16715(o: Option) { + if let Some(x) = o { + if x > 0 { + println!("Positive: {}", x); + } + + #[cfg(feature = "some_feature")] + { + if x % 2 == 0 { + println!("Even: {}", x); + } + } + } +} diff --git a/tests/ui/collapsible_match.rs b/tests/ui/collapsible_match.rs index 84f958ee8458..98f2fcfdf479 100644 --- a/tests/ui/collapsible_match.rs +++ b/tests/ui/collapsible_match.rs @@ -389,3 +389,58 @@ fn foo(t: T) -> U { fn take(t: T) {} fn main() {} + +fn issue16705(x: Option) { + fn takes_ownership(s: String) -> bool { + true + } + fn borrows_mut(s: &mut str) -> bool { + true + } + + let _ = match x { + Some(val) => { + if takes_ownership(val) { + return; + } else { + false + } + }, + _ => false, + }; + + let mut x: Option<&mut str> = Some(&mut String::new()); + let _ = match x { + Some(val) => { + if borrows_mut(val) { + return; + } else { + false + } + }, + _ => false, + }; + + let mut x = Some(String::new()); + let _ = match x { + Some(ref mut val) => { + if borrows_mut(val) { + return; + } else { + false + } + }, + _ => false, + }; + + let _ = match &mut x { + Some(val) => { + if borrows_mut(val) { + return; + } else { + false + } + }, + _ => false, + }; +} diff --git a/tests/ui/explicit_counter_loop.rs b/tests/ui/explicit_counter_loop.rs index 79968700f8e4..a8145d16c149 100644 --- a/tests/ui/explicit_counter_loop.rs +++ b/tests/ui/explicit_counter_loop.rs @@ -332,3 +332,34 @@ fn add_assign(&mut self, rhs: u8) { priority += 1; } } + +pub fn issue_16642() { + let mut base = 100; + const MAX: usize = 10; + for _ in 0..MAX { + //~^ explicit_counter_loop + base += 1; + } + + let mut base = 100; + + let nums = vec![1, 2, 3, 4]; + for _ in nums { + //~^ explicit_counter_loop + base += 1; + } + + // inclusive range: should not suggest .take() + let mut base = 100; + for _ in 0..=MAX { + //~^ explicit_counter_loop + base += 1; + } + + // non-zero start: should not suggest .take(), falls through to zip + let mut base = 100; + for _ in 5..MAX { + //~^ explicit_counter_loop + base += 1; + } +} diff --git a/tests/ui/explicit_counter_loop.stderr b/tests/ui/explicit_counter_loop.stderr index 7a83df05ec0a..eb6be74c8805 100644 --- a/tests/ui/explicit_counter_loop.stderr +++ b/tests/ui/explicit_counter_loop.stderr @@ -81,5 +81,29 @@ error: the variable `j` is used as a loop counter LL | for item in &v { | ^^^^^^^^^^^^^^ help: consider using: `for (j, item) in (s + 1..).zip(v.iter())` -error: aborting due to 13 previous errors +error: the variable `base` is used as a loop counter + --> tests/ui/explicit_counter_loop.rs:339:5 + | +LL | for _ in 0..MAX { + | ^^^^^^^^^^^^^^^ help: consider using: `for base in (100..).take(MAX)` + +error: the variable `base` is used as a loop counter + --> tests/ui/explicit_counter_loop.rs:347:5 + | +LL | for _ in nums { + | ^^^^^^^^^^^^^ help: consider using: `for (base, _) in (100..).zip(nums.into_iter())` + +error: the variable `base` is used as a loop counter + --> tests/ui/explicit_counter_loop.rs:354:5 + | +LL | for _ in 0..=MAX { + | ^^^^^^^^^^^^^^^^ help: consider using: `for (base, _) in (100..).zip((0..=MAX))` + +error: the variable `base` is used as a loop counter + --> tests/ui/explicit_counter_loop.rs:361:5 + | +LL | for _ in 5..MAX { + | ^^^^^^^^^^^^^^^ help: consider using: `for (base, _) in (100..).zip((5..MAX))` + +error: aborting due to 17 previous errors diff --git a/tests/ui/iter_kv_map.fixed b/tests/ui/iter_kv_map.fixed index e3ab5fd1e9ef..e99ea43efc8e 100644 --- a/tests/ui/iter_kv_map.fixed +++ b/tests/ui/iter_kv_map.fixed @@ -231,3 +231,15 @@ fn issue16515() { hash_map.into_values().filter_map(|v| (v > 0).then_some(1)); //~^ iter_kv_map } + +fn issue16742() { + let map: HashMap> = HashMap::new(); + map.values().flat_map(|v| v.iter().map(|i| *i + 1)); + //~^ iter_kv_map + map.values().flatten(); + //~^ iter_kv_map + + let map: HashMap> = HashMap::new(); + map.into_values().flatten(); + //~^ iter_kv_map +} diff --git a/tests/ui/iter_kv_map.rs b/tests/ui/iter_kv_map.rs index 903813b1bf62..7eca0faf6329 100644 --- a/tests/ui/iter_kv_map.rs +++ b/tests/ui/iter_kv_map.rs @@ -235,3 +235,15 @@ fn issue16515() { hash_map.into_iter().filter_map(|(_, v)| (v > 0).then_some(1)); //~^ iter_kv_map } + +fn issue16742() { + let map: HashMap> = HashMap::new(); + map.iter().flat_map(|(_, v)| v.iter().map(|i| *i + 1)); + //~^ iter_kv_map + map.iter().flat_map(|(_, v)| v); + //~^ iter_kv_map + + let map: HashMap> = HashMap::new(); + map.into_iter().flat_map(|(_, v)| v); + //~^ iter_kv_map +} diff --git a/tests/ui/iter_kv_map.stderr b/tests/ui/iter_kv_map.stderr index cdfd05fdd09e..25c4ad283be1 100644 --- a/tests/ui/iter_kv_map.stderr +++ b/tests/ui/iter_kv_map.stderr @@ -323,5 +323,23 @@ error: iterating on a map's values LL | hash_map.into_iter().filter_map(|(_, v)| (v > 0).then_some(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `hash_map.into_values().filter_map(|v| (v > 0).then_some(1))` -error: aborting due to 48 previous errors +error: iterating on a map's values + --> tests/ui/iter_kv_map.rs:241:5 + | +LL | map.iter().flat_map(|(_, v)| v.iter().map(|i| *i + 1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().flat_map(|v| v.iter().map(|i| *i + 1))` + +error: iterating on a map's values + --> tests/ui/iter_kv_map.rs:243:5 + | +LL | map.iter().flat_map(|(_, v)| v); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.values().flatten()` + +error: iterating on a map's values + --> tests/ui/iter_kv_map.rs:247:5 + | +LL | map.into_iter().flat_map(|(_, v)| v); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `map.into_values().flatten()` + +error: aborting due to 51 previous errors diff --git a/tests/ui/manual_noop_waker.rs b/tests/ui/manual_noop_waker.rs new file mode 100644 index 000000000000..9b4dd90e273c --- /dev/null +++ b/tests/ui/manual_noop_waker.rs @@ -0,0 +1,40 @@ +#![warn(clippy::manual_noop_waker)] +use std::sync::Arc; +use std::task::Wake; + +struct PartialWaker; +impl Wake for PartialWaker { + //~^ ERROR: manual implementation of a no-op waker + fn wake(self: Arc) {} +} + +struct MyWakerPartial; +impl Wake for MyWakerPartial { + //~^ manual_noop_waker + fn wake(self: Arc) {} + // wake_by_ref not implemented, uses default +} + +trait CustomWake { + fn wake(self); +} + +impl CustomWake for () { + fn wake(self) {} +} + +mod custom_module { + use std::sync::Arc; + + // Custom Wake trait that should NOT trigger the lint + pub trait Wake { + fn wake(self: Arc); + fn wake_by_ref(self: &Arc); + } + + pub struct CustomWaker; + impl Wake for CustomWaker { + fn wake(self: Arc) {} + fn wake_by_ref(self: &Arc) {} + } +} diff --git a/tests/ui/manual_noop_waker.stderr b/tests/ui/manual_noop_waker.stderr new file mode 100644 index 000000000000..b3b30f96a08f --- /dev/null +++ b/tests/ui/manual_noop_waker.stderr @@ -0,0 +1,20 @@ +error: manual implementation of a no-op waker + --> tests/ui/manual_noop_waker.rs:6:6 + | +LL | impl Wake for PartialWaker { + | ^^^^ + | + = help: use `std::task::Waker::noop()` instead + = note: `-D clippy::manual-noop-waker` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_noop_waker)]` + +error: manual implementation of a no-op waker + --> tests/ui/manual_noop_waker.rs:12:6 + | +LL | impl Wake for MyWakerPartial { + | ^^^^ + | + = help: use `std::task::Waker::noop()` instead + +error: aborting due to 2 previous errors + diff --git a/tests/ui/manual_option_zip.fixed b/tests/ui/manual_option_zip.fixed new file mode 100644 index 000000000000..c0ab66e19169 --- /dev/null +++ b/tests/ui/manual_option_zip.fixed @@ -0,0 +1,118 @@ +#![warn(clippy::manual_option_zip)] +#![allow(clippy::bind_instead_of_map)] + +fn main() {} + +fn should_lint() { + // basic case + let a: Option = Some(1); + let b: Option = Some(2); + let _ = a.zip(b); + //~^ manual_option_zip + + // different types + let a: Option = Some(String::new()); + let b: Option = Some(1); + let _ = a.zip(b); + //~^ manual_option_zip + + // with None receiver + let b: Option = Some(2); + let _ = None::.zip(b); + //~^ manual_option_zip + + // with function call as map receiver + let a: Option = Some(1); + let _ = a.zip(get_option()); + //~^ manual_option_zip + + // tuple order reversed: (inner, outer) instead of (outer, inner) + let a: Option = Some(1); + let b: Option = Some(2); + let _ = b.zip(a); + //~^ manual_option_zip + + // closure bodies wrapped in blocks + let a: Option = Some(1); + let b: Option = Some(2); + #[rustfmt::skip] + let _ = a.zip(b); + //~^ manual_option_zip + #[rustfmt::skip] + let _ = a.zip(b); + //~^ manual_option_zip + #[rustfmt::skip] + let _ = a.zip(b); + //~^ manual_option_zip +} + +fn should_not_lint() { + let a: Option = Some(1); + let b: Option = Some(2); + + // tuple has more than 2 elements + let _ = a.and_then(|a| b.map(|b| (a, b, 1))); + + // three-element tuple but with either `a` or `b` as the elements + let _ = a.and_then(|a| b.map(|b| (a, b, a))); + + // inner closure body is not a simple tuple of the params + let _ = a.and_then(|a| b.map(|b| (a, b + 1))); + + // map receiver uses the outer closure parameter + let _ = a.and_then(|a| a.checked_add(1).map(|b| (a, b))); + + // .map receiver is not an Option type. + let _ = a.and_then(|a| NotOption(Some(1)).map(|b| (a, b))); + + // .and_then receiver is not an Option type. + let _ = NotOption(Some(1)).and_then(|a| b.map(|b| (a, b))); + + // closure body is not a map call + let a: Option = Some(1); + let _ = a.and_then(|a| Some((a, 1))); + + // single-element tuple + let _ = a.and_then(|a| b.map(|_b| (a,))); + + // the outer param used in the map receiver (cannot extract) + let opts: Vec> = vec![Some(1), Some(2)]; + let _ = a.and_then(|a| opts[a as usize].map(|b| (a, b))); + + // extra statements in outer closure body + let _ = a.and_then(|a| { + let _x = 1; + b.map(|b| (a, b)) + }); + + // extra statements in inner closure body + let _ = a.and_then(|a| { + b.map(|b| { + let _x = 1; + (a, b) + }) + }); + + // n-ary zip where n > 2, which is out of scope for this lint (for now) + let c: Option = Some(3); + let _ = a.and_then(|a| b.and_then(|b| c.map(|c| (a, b, c)))); + + // not Option type (Result) + let a: Result = Ok(1); + let b: Result = Ok(2); + let _ = a.and_then(|a| b.map(|b| (a, b))); +} + +fn get_option() -> Option { + Some(123) +} + +struct NotOption(Option); +impl NotOption { + fn map(self, f: impl FnOnce(i32) -> U) -> Option { + self.0.map(f) + } + fn and_then(self, f: impl FnOnce(i32) -> Option) -> Option { + self.0.and_then(f) + } +} diff --git a/tests/ui/manual_option_zip.rs b/tests/ui/manual_option_zip.rs new file mode 100644 index 000000000000..578f26cea637 --- /dev/null +++ b/tests/ui/manual_option_zip.rs @@ -0,0 +1,118 @@ +#![warn(clippy::manual_option_zip)] +#![allow(clippy::bind_instead_of_map)] + +fn main() {} + +fn should_lint() { + // basic case + let a: Option = Some(1); + let b: Option = Some(2); + let _ = a.and_then(|a| b.map(|b| (a, b))); + //~^ manual_option_zip + + // different types + let a: Option = Some(String::new()); + let b: Option = Some(1); + let _ = a.and_then(|a| b.map(|b| (a, b))); + //~^ manual_option_zip + + // with None receiver + let b: Option = Some(2); + let _ = None::.and_then(|a| b.map(|b| (a, b))); + //~^ manual_option_zip + + // with function call as map receiver + let a: Option = Some(1); + let _ = a.and_then(|a| get_option().map(|b| (a, b))); + //~^ manual_option_zip + + // tuple order reversed: (inner, outer) instead of (outer, inner) + let a: Option = Some(1); + let b: Option = Some(2); + let _ = a.and_then(|a| b.map(|b| (b, a))); + //~^ manual_option_zip + + // closure bodies wrapped in blocks + let a: Option = Some(1); + let b: Option = Some(2); + #[rustfmt::skip] + let _ = a.and_then(|a| { b.map(|b| (a, b)) }); + //~^ manual_option_zip + #[rustfmt::skip] + let _ = a.and_then(|a| b.map(|b| { (a, b) })); + //~^ manual_option_zip + #[rustfmt::skip] + let _ = a.and_then(|a| { b.map(|b| { (a, b) }) }); + //~^ manual_option_zip +} + +fn should_not_lint() { + let a: Option = Some(1); + let b: Option = Some(2); + + // tuple has more than 2 elements + let _ = a.and_then(|a| b.map(|b| (a, b, 1))); + + // three-element tuple but with either `a` or `b` as the elements + let _ = a.and_then(|a| b.map(|b| (a, b, a))); + + // inner closure body is not a simple tuple of the params + let _ = a.and_then(|a| b.map(|b| (a, b + 1))); + + // map receiver uses the outer closure parameter + let _ = a.and_then(|a| a.checked_add(1).map(|b| (a, b))); + + // .map receiver is not an Option type. + let _ = a.and_then(|a| NotOption(Some(1)).map(|b| (a, b))); + + // .and_then receiver is not an Option type. + let _ = NotOption(Some(1)).and_then(|a| b.map(|b| (a, b))); + + // closure body is not a map call + let a: Option = Some(1); + let _ = a.and_then(|a| Some((a, 1))); + + // single-element tuple + let _ = a.and_then(|a| b.map(|_b| (a,))); + + // the outer param used in the map receiver (cannot extract) + let opts: Vec> = vec![Some(1), Some(2)]; + let _ = a.and_then(|a| opts[a as usize].map(|b| (a, b))); + + // extra statements in outer closure body + let _ = a.and_then(|a| { + let _x = 1; + b.map(|b| (a, b)) + }); + + // extra statements in inner closure body + let _ = a.and_then(|a| { + b.map(|b| { + let _x = 1; + (a, b) + }) + }); + + // n-ary zip where n > 2, which is out of scope for this lint (for now) + let c: Option = Some(3); + let _ = a.and_then(|a| b.and_then(|b| c.map(|c| (a, b, c)))); + + // not Option type (Result) + let a: Result = Ok(1); + let b: Result = Ok(2); + let _ = a.and_then(|a| b.map(|b| (a, b))); +} + +fn get_option() -> Option { + Some(123) +} + +struct NotOption(Option); +impl NotOption { + fn map(self, f: impl FnOnce(i32) -> U) -> Option { + self.0.map(f) + } + fn and_then(self, f: impl FnOnce(i32) -> Option) -> Option { + self.0.and_then(f) + } +} diff --git a/tests/ui/manual_option_zip.stderr b/tests/ui/manual_option_zip.stderr new file mode 100644 index 000000000000..473f21702654 --- /dev/null +++ b/tests/ui/manual_option_zip.stderr @@ -0,0 +1,53 @@ +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:10:13 + | +LL | let _ = a.and_then(|a| b.map(|b| (a, b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(b)` + | + = note: `-D clippy::manual-option-zip` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_option_zip)]` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:16:13 + | +LL | let _ = a.and_then(|a| b.map(|b| (a, b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(b)` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:21:13 + | +LL | let _ = None::.and_then(|a| b.map(|b| (a, b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `None::.zip(b)` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:26:13 + | +LL | let _ = a.and_then(|a| get_option().map(|b| (a, b))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(get_option())` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:32:13 + | +LL | let _ = a.and_then(|a| b.map(|b| (b, a))); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `b.zip(a)` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:39:13 + | +LL | let _ = a.and_then(|a| { b.map(|b| (a, b)) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(b)` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:42:13 + | +LL | let _ = a.and_then(|a| b.map(|b| { (a, b) })); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(b)` + +error: manual implementation of `Option::zip` + --> tests/ui/manual_option_zip.rs:45:13 + | +LL | let _ = a.and_then(|a| { b.map(|b| { (a, b) }) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `a.zip(b)` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/manual_pop_if.fixed b/tests/ui/manual_pop_if.fixed index 6e30786c8097..ba3fe7cb0156 100644 --- a/tests/ui/manual_pop_if.fixed +++ b/tests/ui/manual_pop_if.fixed @@ -1,9 +1,12 @@ #![warn(clippy::manual_pop_if)] #![allow(clippy::collapsible_if, clippy::redundant_closure)] +#![feature(binary_heap_pop_if)] -use std::collections::VecDeque; +use std::collections::{BinaryHeap, VecDeque}; use std::marker::PhantomData; +fn main() {} + // FakeVec has the same methods as Vec but isn't actually a Vec struct FakeVec(PhantomData); @@ -17,14 +20,24 @@ impl FakeVec { } } -fn is_some_and_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn is_some_and_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if + vec.pop_if(|x| *x > 2); + + //~v manual_pop_if deque.pop_back_if(|x| *x > 2); + //~v manual_pop_if deque.pop_front_if(|x| *x > 2); + + //~v manual_pop_if + heap.pop_if(|x| *x > 2); } fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { @@ -40,24 +53,6 @@ fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { fake_vec.pop().unwrap(); } - // Do not lint, else-if branch - if false { - // something - } else if vec.last().is_some_and(|x| *x > 2) { - vec.pop().unwrap(); - } - - // Do not lint, value used in let binding - if vec.last().is_some_and(|x| *x > 2) { - let _value = vec.pop().unwrap(); - println!("Popped: {}", _value); - } - - // Do not lint, value used in expression - if vec.last().is_some_and(|x| *x > 2) { - println!("Popped: {}", vec.pop().unwrap()); - } - // Do not lint, else block let _result = if vec.last().is_some_and(|x| *x > 2) { vec.pop().unwrap() @@ -66,14 +61,24 @@ fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { }; } -fn if_let_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn if_let_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if + vec.pop_if(|x| *x > 2); + + //~v manual_pop_if deque.pop_back_if(|x| *x > 2); + //~v manual_pop_if deque.pop_front_if(|x| *x > 2); + + //~v manual_pop_if + heap.pop_if(|x| *x > 2); } fn if_let_pattern_negative(mut vec: Vec) { @@ -100,13 +105,6 @@ fn if_let_pattern_negative(mut vec: Vec) { } } - // Do not lint, value used in let binding - if let Some(x) = vec.last() { - if *x > 2 { - let _val = vec.pop().unwrap(); - } - } - // Do not lint, else block let _result = if let Some(x) = vec.last() { if *x > 2 { vec.pop().unwrap() } else { 0 } @@ -115,7 +113,9 @@ fn if_let_pattern_negative(mut vec: Vec) { }; } -fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + vec.pop_if(|x| *x > 2); + vec.pop_if(|x| *x > 2); vec.pop_if(|x| *x > 2); @@ -123,6 +123,8 @@ fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque) { deque.pop_back_if(|x| *x > 2); deque.pop_front_if(|x| *x > 2); + + heap.pop_if(|x| *x > 2); } fn let_chain_pattern_negative(mut vec: Vec) { @@ -141,20 +143,6 @@ fn let_chain_pattern_negative(mut vec: Vec) { vec.pop().unwrap(); } - // Do not lint, value used in let binding - if let Some(x) = vec.last() - && *x > 2 - { - let _val = vec.pop().unwrap(); - } - - // Do not lint, value used in expression - if let Some(x) = vec.last() - && *x > 2 - { - println!("Popped: {}", vec.pop().unwrap()); - } - // Do not lint, else block let _result = if let Some(x) = vec.last() && *x > 2 @@ -165,14 +153,24 @@ fn let_chain_pattern_negative(mut vec: Vec) { }; } -fn map_unwrap_or_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn map_unwrap_or_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if vec.pop_if(|x| *x > 2); + //~v manual_pop_if + vec.pop_if(|x| *x > 2); + + //~v manual_pop_if deque.pop_back_if(|x| *x > 2); + //~v manual_pop_if deque.pop_front_if(|x| *x > 2); + + //~v manual_pop_if + heap.pop_if(|x| *x > 2); } fn map_unwrap_or_pattern_negative(mut vec: Vec) { @@ -198,11 +196,6 @@ fn map_unwrap_or_pattern_negative(mut vec: Vec) { vec.pop().unwrap(); } - // Do not lint, value used in let binding - if vec.last().map(|x| *x > 2).unwrap_or(false) { - let _val = vec.pop().unwrap(); - } - // Do not lint, else block let _result = if vec.last().map(|x| *x > 2).unwrap_or(false) { vec.pop().unwrap() @@ -213,6 +206,7 @@ fn map_unwrap_or_pattern_negative(mut vec: Vec) { // this makes sure we do not expand vec![] in the suggestion fn handle_macro_in_closure(mut vec: Vec>) { + //~v manual_pop_if vec.pop_if(|e| *e == vec![1]); } @@ -236,12 +230,15 @@ fn msrv_too_low_vecdeque(mut deque: VecDeque) { #[clippy::msrv = "1.86.0"] fn msrv_high_enough_vec(mut vec: Vec) { + //~v manual_pop_if vec.pop_if(|x| *x > 2); } #[clippy::msrv = "1.93.0"] fn msrv_high_enough_vecdeque(mut deque: VecDeque) { + //~v manual_pop_if deque.pop_back_if(|x| *x > 2); + //~v manual_pop_if deque.pop_front_if(|x| *x > 2); } diff --git a/tests/ui/manual_pop_if.rs b/tests/ui/manual_pop_if.rs index a2f679dfc6b3..483cf12bd8de 100644 --- a/tests/ui/manual_pop_if.rs +++ b/tests/ui/manual_pop_if.rs @@ -1,9 +1,12 @@ #![warn(clippy::manual_pop_if)] #![allow(clippy::collapsible_if, clippy::redundant_closure)] +#![feature(binary_heap_pop_if)] -use std::collections::VecDeque; +use std::collections::{BinaryHeap, VecDeque}; use std::marker::PhantomData; +fn main() {} + // FakeVec has the same methods as Vec but isn't actually a Vec struct FakeVec(PhantomData); @@ -17,26 +20,36 @@ fn pop(&mut self) -> Option { } } -fn is_some_and_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn is_some_and_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if if vec.last().is_some_and(|x| *x > 2) { - //~^ manual_pop_if vec.pop().unwrap(); } + //~v manual_pop_if + if vec.last().is_some_and(|x| *x > 2) { + unsafe { vec.pop().unwrap_unchecked() }; + } + + //~v manual_pop_if if vec.last().is_some_and(|x| *x > 2) { - //~^ manual_pop_if vec.pop().expect("element"); } + //~v manual_pop_if if deque.back().is_some_and(|x| *x > 2) { - //~^ manual_pop_if deque.pop_back().unwrap(); } + //~v manual_pop_if if deque.front().is_some_and(|x| *x > 2) { - //~^ manual_pop_if deque.pop_front().unwrap(); } + + //~v manual_pop_if + if heap.peek().is_some_and(|x| *x > 2) { + heap.pop().unwrap(); + } } fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { @@ -52,24 +65,6 @@ fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { fake_vec.pop().unwrap(); } - // Do not lint, else-if branch - if false { - // something - } else if vec.last().is_some_and(|x| *x > 2) { - vec.pop().unwrap(); - } - - // Do not lint, value used in let binding - if vec.last().is_some_and(|x| *x > 2) { - let _value = vec.pop().unwrap(); - println!("Popped: {}", _value); - } - - // Do not lint, value used in expression - if vec.last().is_some_and(|x| *x > 2) { - println!("Popped: {}", vec.pop().unwrap()); - } - // Do not lint, else block let _result = if vec.last().is_some_and(|x| *x > 2) { vec.pop().unwrap() @@ -78,34 +73,48 @@ fn is_some_and_pattern_negative(mut vec: Vec, mut deque: VecDeque) { }; } -fn if_let_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn if_let_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if if let Some(x) = vec.last() { - //~^ manual_pop_if if *x > 2 { vec.pop().unwrap(); } } + //~v manual_pop_if + if let Some(x) = vec.last() { + if *x > 2 { + unsafe { vec.pop().unwrap_unchecked() }; + } + } + + //~v manual_pop_if if let Some(x) = vec.last() { - //~^ manual_pop_if if *x > 2 { vec.pop().expect("element"); } } + //~v manual_pop_if if let Some(x) = deque.back() { - //~^ manual_pop_if if *x > 2 { deque.pop_back().unwrap(); } } + //~v manual_pop_if if let Some(x) = deque.front() { - //~^ manual_pop_if if *x > 2 { deque.pop_front().unwrap(); } } + + //~v manual_pop_if + if let Some(x) = heap.peek() { + if *x > 2 { + heap.pop().unwrap(); + } + } } fn if_let_pattern_negative(mut vec: Vec) { @@ -132,13 +141,6 @@ fn if_let_pattern_negative(mut vec: Vec) { } } - // Do not lint, value used in let binding - if let Some(x) = vec.last() { - if *x > 2 { - let _val = vec.pop().unwrap(); - } - } - // Do not lint, else block let _result = if let Some(x) = vec.last() { if *x > 2 { vec.pop().unwrap() } else { 0 } @@ -147,13 +149,19 @@ fn if_let_pattern_negative(mut vec: Vec) { }; } -fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { if let Some(x) = vec.last() //~ manual_pop_if && *x > 2 { vec.pop().unwrap(); } + if let Some(x) = vec.last() //~ manual_pop_if + && *x > 2 + { + unsafe { vec.pop().unwrap_unchecked() }; + } + if let Some(x) = vec.last() //~ manual_pop_if && *x > 2 { @@ -171,6 +179,12 @@ fn let_chain_pattern_positive(mut vec: Vec, mut deque: VecDeque) { { deque.pop_front().unwrap(); } + + if let Some(x) = heap.peek() //~ manual_pop_if + && *x > 2 + { + heap.pop().unwrap(); + } } fn let_chain_pattern_negative(mut vec: Vec) { @@ -189,20 +203,6 @@ fn let_chain_pattern_negative(mut vec: Vec) { vec.pop().unwrap(); } - // Do not lint, value used in let binding - if let Some(x) = vec.last() - && *x > 2 - { - let _val = vec.pop().unwrap(); - } - - // Do not lint, value used in expression - if let Some(x) = vec.last() - && *x > 2 - { - println!("Popped: {}", vec.pop().unwrap()); - } - // Do not lint, else block let _result = if let Some(x) = vec.last() && *x > 2 @@ -213,26 +213,36 @@ fn let_chain_pattern_negative(mut vec: Vec) { }; } -fn map_unwrap_or_pattern_positive(mut vec: Vec, mut deque: VecDeque) { +fn map_unwrap_or_pattern_positive(mut vec: Vec, mut deque: VecDeque, mut heap: BinaryHeap) { + //~v manual_pop_if if vec.last().map(|x| *x > 2).unwrap_or(false) { - //~^ manual_pop_if vec.pop().unwrap(); } + //~v manual_pop_if + if vec.last().map(|x| *x > 2).unwrap_or(false) { + unsafe { vec.pop().unwrap_unchecked() }; + } + + //~v manual_pop_if if vec.last().map(|x| *x > 2).unwrap_or(false) { - //~^ manual_pop_if vec.pop().expect("element"); } + //~v manual_pop_if if deque.back().map(|x| *x > 2).unwrap_or(false) { - //~^ manual_pop_if deque.pop_back().unwrap(); } + //~v manual_pop_if if deque.front().map(|x| *x > 2).unwrap_or(false) { - //~^ manual_pop_if deque.pop_front().unwrap(); } + + //~v manual_pop_if + if heap.peek().map(|x| *x > 2).unwrap_or(false) { + heap.pop().unwrap(); + } } fn map_unwrap_or_pattern_negative(mut vec: Vec) { @@ -258,11 +268,6 @@ fn map_unwrap_or_pattern_negative(mut vec: Vec) { vec.pop().unwrap(); } - // Do not lint, value used in let binding - if vec.last().map(|x| *x > 2).unwrap_or(false) { - let _val = vec.pop().unwrap(); - } - // Do not lint, else block let _result = if vec.last().map(|x| *x > 2).unwrap_or(false) { vec.pop().unwrap() @@ -273,8 +278,8 @@ fn map_unwrap_or_pattern_negative(mut vec: Vec) { // this makes sure we do not expand vec![] in the suggestion fn handle_macro_in_closure(mut vec: Vec>) { + //~v manual_pop_if if vec.last().is_some_and(|e| *e == vec![1]) { - //~^ manual_pop_if vec.pop().unwrap(); } } @@ -299,21 +304,21 @@ fn msrv_too_low_vecdeque(mut deque: VecDeque) { #[clippy::msrv = "1.86.0"] fn msrv_high_enough_vec(mut vec: Vec) { + //~v manual_pop_if if vec.last().is_some_and(|x| *x > 2) { - //~^ manual_pop_if vec.pop().unwrap(); } } #[clippy::msrv = "1.93.0"] fn msrv_high_enough_vecdeque(mut deque: VecDeque) { + //~v manual_pop_if if deque.back().is_some_and(|x| *x > 2) { - //~^ manual_pop_if deque.pop_back().unwrap(); } + //~v manual_pop_if if deque.front().is_some_and(|x| *x > 2) { - //~^ manual_pop_if deque.pop_front().unwrap(); } } diff --git a/tests/ui/manual_pop_if.stderr b/tests/ui/manual_pop_if.stderr index d57e20dc6205..ff12b7dad707 100644 --- a/tests/ui/manual_pop_if.stderr +++ b/tests/ui/manual_pop_if.stderr @@ -1,197 +1,500 @@ error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:21:5 + --> tests/ui/manual_pop_if.rs:25:5 | -LL | / if vec.last().is_some_and(|x| *x > 2) { -LL | | -LL | | vec.pop().unwrap(); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::manual-pop-if` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::manual_pop_if)]` +help: try + | +LL - if vec.last().is_some_and(|x| *x > 2) { +LL - vec.pop().unwrap(); +LL - } +LL + vec.pop_if(|x| *x > 2); + | error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:26:5 + --> tests/ui/manual_pop_if.rs:30:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { vec.pop().unwrap_unchecked() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().is_some_and(|x| *x > 2) { +LL - unsafe { vec.pop().unwrap_unchecked() }; +LL - } +LL + vec.pop_if(|x| *x > 2); + | + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if.rs:35:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().expect("element"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().is_some_and(|x| *x > 2) { +LL - vec.pop().expect("element"); +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if vec.last().is_some_and(|x| *x > 2) { -LL | | -LL | | vec.pop().expect("element"); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_back_if` - --> tests/ui/manual_pop_if.rs:31:5 + --> tests/ui/manual_pop_if.rs:40:5 + | +LL | if deque.back().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_back().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.back().is_some_and(|x| *x > 2) { +LL - deque.pop_back().unwrap(); +LL - } +LL + deque.pop_back_if(|x| *x > 2); | -LL | / if deque.back().is_some_and(|x| *x > 2) { -LL | | -LL | | deque.pop_back().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_back_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_front_if` - --> tests/ui/manual_pop_if.rs:36:5 + --> tests/ui/manual_pop_if.rs:45:5 + | +LL | if deque.front().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_front().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.front().is_some_and(|x| *x > 2) { +LL - deque.pop_front().unwrap(); +LL - } +LL + deque.pop_front_if(|x| *x > 2); + | + +error: manual implementation of `BinaryHeap::pop_if` + --> tests/ui/manual_pop_if.rs:50:5 + | +LL | if heap.peek().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | heap.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if heap.peek().is_some_and(|x| *x > 2) { +LL - heap.pop().unwrap(); +LL - } +LL + heap.pop_if(|x| *x > 2); | -LL | / if deque.front().is_some_and(|x| *x > 2) { -LL | | -LL | | deque.pop_front().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_front_if(|x| *x > 2);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:82:5 + --> tests/ui/manual_pop_if.rs:78:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() { +LL - if *x > 2 { +LL - vec.pop().unwrap(); +LL - } +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if let Some(x) = vec.last() { -LL | | -LL | | if *x > 2 { -LL | | vec.pop().unwrap(); -LL | | } -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:89:5 + --> tests/ui/manual_pop_if.rs:85:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | unsafe { vec.pop().unwrap_unchecked() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() { +LL - if *x > 2 { +LL - unsafe { vec.pop().unwrap_unchecked() }; +LL - } +LL - } +LL + vec.pop_if(|x| *x > 2); + | + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if.rs:92:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | vec.pop().expect("element"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() { +LL - if *x > 2 { +LL - vec.pop().expect("element"); +LL - } +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if let Some(x) = vec.last() { -LL | | -LL | | if *x > 2 { -LL | | vec.pop().expect("element"); -LL | | } -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_back_if` - --> tests/ui/manual_pop_if.rs:96:5 + --> tests/ui/manual_pop_if.rs:99:5 + | +LL | if let Some(x) = deque.back() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | deque.pop_back().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = deque.back() { +LL - if *x > 2 { +LL - deque.pop_back().unwrap(); +LL - } +LL - } +LL + deque.pop_back_if(|x| *x > 2); | -LL | / if let Some(x) = deque.back() { -LL | | -LL | | if *x > 2 { -LL | | deque.pop_back().unwrap(); -LL | | } -LL | | } - | |_____^ help: try: `deque.pop_back_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_front_if` - --> tests/ui/manual_pop_if.rs:103:5 + --> tests/ui/manual_pop_if.rs:106:5 + | +LL | if let Some(x) = deque.front() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | deque.pop_front().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = deque.front() { +LL - if *x > 2 { +LL - deque.pop_front().unwrap(); +LL - } +LL - } +LL + deque.pop_front_if(|x| *x > 2); + | + +error: manual implementation of `BinaryHeap::pop_if` + --> tests/ui/manual_pop_if.rs:113:5 + | +LL | if let Some(x) = heap.peek() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | heap.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = heap.peek() { +LL - if *x > 2 { +LL - heap.pop().unwrap(); +LL - } +LL - } +LL + heap.pop_if(|x| *x > 2); | -LL | / if let Some(x) = deque.front() { -LL | | -LL | | if *x > 2 { -LL | | deque.pop_front().unwrap(); -LL | | } -LL | | } - | |_____^ help: try: `deque.pop_front_if(|x| *x > 2);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:151:5 + --> tests/ui/manual_pop_if.rs:153:5 | LL | / if let Some(x) = vec.last() LL | | && *x > 2 -LL | | { -LL | | vec.pop().unwrap(); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` + | |_________________^ +LL | { +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() +LL - && *x > 2 +LL - { +LL - vec.pop().unwrap(); +LL - } +LL + vec.pop_if(|x| *x > 2); + | error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:157:5 + --> tests/ui/manual_pop_if.rs:159:5 | LL | / if let Some(x) = vec.last() LL | | && *x > 2 -LL | | { -LL | | vec.pop().expect("element"); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` + | |_________________^ +LL | { +LL | unsafe { vec.pop().unwrap_unchecked() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() +LL - && *x > 2 +LL - { +LL - unsafe { vec.pop().unwrap_unchecked() }; +LL - } +LL + vec.pop_if(|x| *x > 2); + | + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if.rs:165:5 + | +LL | / if let Some(x) = vec.last() +LL | | && *x > 2 + | |_________________^ +LL | { +LL | vec.pop().expect("element"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = vec.last() +LL - && *x > 2 +LL - { +LL - vec.pop().expect("element"); +LL - } +LL + vec.pop_if(|x| *x > 2); + | error: manual implementation of `VecDeque::pop_back_if` - --> tests/ui/manual_pop_if.rs:163:5 + --> tests/ui/manual_pop_if.rs:171:5 | LL | / if let Some(x) = deque.back() LL | | && *x > 2 -LL | | { -LL | | deque.pop_back().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_back_if(|x| *x > 2);` + | |_________________^ +LL | { +LL | deque.pop_back().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = deque.back() +LL - && *x > 2 +LL - { +LL - deque.pop_back().unwrap(); +LL - } +LL + deque.pop_back_if(|x| *x > 2); + | error: manual implementation of `VecDeque::pop_front_if` - --> tests/ui/manual_pop_if.rs:169:5 + --> tests/ui/manual_pop_if.rs:177:5 | LL | / if let Some(x) = deque.front() LL | | && *x > 2 -LL | | { -LL | | deque.pop_front().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_front_if(|x| *x > 2);` + | |_________________^ +LL | { +LL | deque.pop_front().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = deque.front() +LL - && *x > 2 +LL - { +LL - deque.pop_front().unwrap(); +LL - } +LL + deque.pop_front_if(|x| *x > 2); + | + +error: manual implementation of `BinaryHeap::pop_if` + --> tests/ui/manual_pop_if.rs:183:5 + | +LL | / if let Some(x) = heap.peek() +LL | | && *x > 2 + | |_________________^ +LL | { +LL | heap.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if let Some(x) = heap.peek() +LL - && *x > 2 +LL - { +LL - heap.pop().unwrap(); +LL - } +LL + heap.pop_if(|x| *x > 2); + | error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:217:5 + --> tests/ui/manual_pop_if.rs:218:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().map(|x| *x > 2).unwrap_or(false) { +LL - vec.pop().unwrap(); +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if vec.last().map(|x| *x > 2).unwrap_or(false) { -LL | | -LL | | vec.pop().unwrap(); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:222:5 + --> tests/ui/manual_pop_if.rs:223:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { vec.pop().unwrap_unchecked() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().map(|x| *x > 2).unwrap_or(false) { +LL - unsafe { vec.pop().unwrap_unchecked() }; +LL - } +LL + vec.pop_if(|x| *x > 2); + | + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if.rs:228:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().expect("element"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().map(|x| *x > 2).unwrap_or(false) { +LL - vec.pop().expect("element"); +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if vec.last().map(|x| *x > 2).unwrap_or(false) { -LL | | -LL | | vec.pop().expect("element"); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_back_if` - --> tests/ui/manual_pop_if.rs:227:5 + --> tests/ui/manual_pop_if.rs:233:5 + | +LL | if deque.back().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_back().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.back().map(|x| *x > 2).unwrap_or(false) { +LL - deque.pop_back().unwrap(); +LL - } +LL + deque.pop_back_if(|x| *x > 2); | -LL | / if deque.back().map(|x| *x > 2).unwrap_or(false) { -LL | | -LL | | deque.pop_back().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_back_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_front_if` - --> tests/ui/manual_pop_if.rs:232:5 + --> tests/ui/manual_pop_if.rs:238:5 + | +LL | if deque.front().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_front().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.front().map(|x| *x > 2).unwrap_or(false) { +LL - deque.pop_front().unwrap(); +LL - } +LL + deque.pop_front_if(|x| *x > 2); + | + +error: manual implementation of `BinaryHeap::pop_if` + --> tests/ui/manual_pop_if.rs:243:5 + | +LL | if heap.peek().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | heap.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if heap.peek().map(|x| *x > 2).unwrap_or(false) { +LL - heap.pop().unwrap(); +LL - } +LL + heap.pop_if(|x| *x > 2); | -LL | / if deque.front().map(|x| *x > 2).unwrap_or(false) { -LL | | -LL | | deque.pop_front().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_front_if(|x| *x > 2);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:276:5 + --> tests/ui/manual_pop_if.rs:282:5 + | +LL | if vec.last().is_some_and(|e| *e == vec![1]) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().is_some_and(|e| *e == vec![1]) { +LL - vec.pop().unwrap(); +LL - } +LL + vec.pop_if(|e| *e == vec![1]); | -LL | / if vec.last().is_some_and(|e| *e == vec![1]) { -LL | | -LL | | vec.pop().unwrap(); -LL | | } - | |_____^ help: try: `vec.pop_if(|e| *e == vec![1]);` error: manual implementation of `Vec::pop_if` - --> tests/ui/manual_pop_if.rs:302:5 + --> tests/ui/manual_pop_if.rs:308:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if vec.last().is_some_and(|x| *x > 2) { +LL - vec.pop().unwrap(); +LL - } +LL + vec.pop_if(|x| *x > 2); | -LL | / if vec.last().is_some_and(|x| *x > 2) { -LL | | -LL | | vec.pop().unwrap(); -LL | | } - | |_____^ help: try: `vec.pop_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_back_if` - --> tests/ui/manual_pop_if.rs:310:5 + --> tests/ui/manual_pop_if.rs:316:5 + | +LL | if deque.back().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_back().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.back().is_some_and(|x| *x > 2) { +LL - deque.pop_back().unwrap(); +LL - } +LL + deque.pop_back_if(|x| *x > 2); | -LL | / if deque.back().is_some_and(|x| *x > 2) { -LL | | -LL | | deque.pop_back().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_back_if(|x| *x > 2);` error: manual implementation of `VecDeque::pop_front_if` - --> tests/ui/manual_pop_if.rs:315:5 + --> tests/ui/manual_pop_if.rs:321:5 + | +LL | if deque.front().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | deque.pop_front().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - if deque.front().is_some_and(|x| *x > 2) { +LL - deque.pop_front().unwrap(); +LL - } +LL + deque.pop_front_if(|x| *x > 2); | -LL | / if deque.front().is_some_and(|x| *x > 2) { -LL | | -LL | | deque.pop_front().unwrap(); -LL | | } - | |_____^ help: try: `deque.pop_front_if(|x| *x > 2);` -error: aborting due to 20 previous errors +error: aborting due to 28 previous errors diff --git a/tests/ui/manual_pop_if_unfixable.rs b/tests/ui/manual_pop_if_unfixable.rs new file mode 100644 index 000000000000..66cdee8289ee --- /dev/null +++ b/tests/ui/manual_pop_if_unfixable.rs @@ -0,0 +1,128 @@ +#![warn(clippy::manual_pop_if)] +#![allow(clippy::collapsible_if, clippy::redundant_closure)] +//@no-rustfix + +fn main() {} + +fn is_some_and_pattern(mut vec: Vec) { + if false { + // something + } else if vec.last().is_some_and(|x| *x > 2) { + vec.pop().unwrap(); + } + //~^^^ manual_pop_if + + //~v manual_pop_if + if vec.last().is_some_and(|x| *x > 2) { + let val = vec.pop().unwrap(); + println!("Popped: {}", val); + } + + //~v manual_pop_if + if vec.last().is_some_and(|x| *x > 2) { + println!("Popped: {}", vec.pop().unwrap()); + } + + //~v manual_pop_if + if vec.last().is_some_and(|x| *x > 2) { + // a comment before the pop + vec.pop().unwrap(); + } + + //~v manual_pop_if + if vec.last().is_some_and(|x| *x > 2) { + vec.pop().unwrap(); + // a comment after the pop + } +} + +fn if_let_pattern(mut vec: Vec) { + //~v manual_pop_if + if let Some(x) = vec.last() { + if *x > 2 { + let val = vec.pop().unwrap(); + println!("Popped: {}", val); + } + } + + //~v manual_pop_if + if let Some(x) = vec.last() { + if *x > 2 { + println!("Popped: {}", vec.pop().unwrap()); + } + } + + //~v manual_pop_if + if let Some(x) = vec.last() { + if *x > 2 { + // a comment before the pop + vec.pop().unwrap(); + } + } + + //~v manual_pop_if + if let Some(x) = vec.last() { + if *x > 2 { + vec.pop().unwrap(); + // a comment after the pop + } + } +} + +fn let_chain_pattern(mut vec: Vec) { + //~v manual_pop_if + if let Some(x) = vec.last() + && *x > 2 + { + let val = vec.pop().unwrap(); + println!("Popped: {}", val); + } + + //~v manual_pop_if + if let Some(x) = vec.last() + && *x > 2 + { + println!("Popped: {}", vec.pop().unwrap()); + } + + //~v manual_pop_if + if let Some(x) = vec.last() + && *x > 2 + { + // a comment before the pop + vec.pop().unwrap(); + } + + //~v manual_pop_if + if let Some(x) = vec.last() + && *x > 2 + { + vec.pop().unwrap(); + // a comment after the pop + } +} + +fn map_unwrap_or_pattern(mut vec: Vec) { + //~v manual_pop_if + if vec.last().map(|x| *x > 2).unwrap_or(false) { + let val = vec.pop().unwrap(); + println!("Popped: {}", val); + } + + //~v manual_pop_if + if vec.last().map(|x| *x > 2).unwrap_or(false) { + println!("Popped: {}", vec.pop().unwrap()); + } + + //~v manual_pop_if + if vec.last().map(|x| *x > 2).unwrap_or(false) { + // a comment before the pop + vec.pop().unwrap(); + } + + //~v manual_pop_if + if vec.last().map(|x| *x > 2).unwrap_or(false) { + vec.pop().unwrap(); + // a comment after the pop + } +} diff --git a/tests/ui/manual_pop_if_unfixable.stderr b/tests/ui/manual_pop_if_unfixable.stderr new file mode 100644 index 000000000000..75e15034a514 --- /dev/null +++ b/tests/ui/manual_pop_if_unfixable.stderr @@ -0,0 +1,193 @@ +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:10:12 + | +LL | } else if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + = note: `-D clippy::manual-pop-if` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_pop_if)]` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:16:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let val = vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:22:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | println!("Popped: {}", vec.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:27:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | // a comment before the pop +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:33:5 + | +LL | if vec.last().is_some_and(|x| *x > 2) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:41:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | let val = vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:49:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | println!("Popped: {}", vec.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:56:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | // a comment before the pop +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:64:5 + | +LL | if let Some(x) = vec.last() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | if *x > 2 { + | ^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:74:5 + | +LL | / if let Some(x) = vec.last() +LL | | && *x > 2 + | |_________________^ +LL | { +LL | let val = vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:82:5 + | +LL | / if let Some(x) = vec.last() +LL | | && *x > 2 + | |_________________^ +LL | { +LL | println!("Popped: {}", vec.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:89:5 + | +LL | / if let Some(x) = vec.last() +LL | | && *x > 2 + | |_________________^ +... +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:97:5 + | +LL | / if let Some(x) = vec.last() +LL | | && *x > 2 + | |_________________^ +LL | { +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:107:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | let val = vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:113:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | println!("Popped: {}", vec.pop().unwrap()); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:118:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | // a comment before the pop +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: manual implementation of `Vec::pop_if` + --> tests/ui/manual_pop_if_unfixable.rs:124:5 + | +LL | if vec.last().map(|x| *x > 2).unwrap_or(false) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | vec.pop().unwrap(); + | ^^^^^^^^^^^^^^^^^^ + | + = help: try refactoring the code using `vec.pop_if(|x| *x > 2);` + +error: aborting due to 17 previous errors + diff --git a/tests/ui/similar_names.rs b/tests/ui/similar_names.rs index 55a141209f0f..4b5e85d0d320 100644 --- a/tests/ui/similar_names.rs +++ b/tests/ui/similar_names.rs @@ -47,11 +47,18 @@ fn main() { let bluby: i32; //~^ similar_names - let cake: i32; + let orange: i32; + let ornange: i32; + //~^ similar_names + let cakes: i32; + let cake: i32; let coke: i32; //~^ similar_names + let wagon: i32; + let twagon: i32; + match 5 { cheese @ 1 => {}, rabbit => panic!(), @@ -93,6 +100,9 @@ fn main() { // 3 letter names are allowed to be similar let kta: i32; let ktv: i32; + + let checked_exp: i32; + let checked_expr: i32; } fn foo() { diff --git a/tests/ui/similar_names.stderr b/tests/ui/similar_names.stderr index c226f73d4db1..7e8d0b2a2b75 100644 --- a/tests/ui/similar_names.stderr +++ b/tests/ui/similar_names.stderr @@ -13,52 +13,64 @@ LL | let blubx: i32; = help: to override `-D warnings` add `#[allow(clippy::similar_names)]` error: binding's name is too similar to existing binding - --> tests/ui/similar_names.rs:52:9 + --> tests/ui/similar_names.rs:51:9 + | +LL | let ornange: i32; + | ^^^^^^^ + | +note: existing binding defined here + --> tests/ui/similar_names.rs:50:9 + | +LL | let orange: i32; + | ^^^^^^ + +error: binding's name is too similar to existing binding + --> tests/ui/similar_names.rs:56:9 | LL | let coke: i32; | ^^^^ | note: existing binding defined here - --> tests/ui/similar_names.rs:50:9 + --> tests/ui/similar_names.rs:55:9 | LL | let cake: i32; | ^^^^ error: binding's name is too similar to existing binding - --> tests/ui/similar_names.rs:71:9 + --> tests/ui/similar_names.rs:78:9 | LL | let xyzeabc: i32; | ^^^^^^^ | note: existing binding defined here - --> tests/ui/similar_names.rs:69:9 + --> tests/ui/similar_names.rs:76:9 | LL | let xyz1abc: i32; | ^^^^^^^ error: binding's name is too similar to existing binding - --> tests/ui/similar_names.rs:76:9 + --> tests/ui/similar_names.rs:83:9 | LL | let parsee: i32; | ^^^^^^ | note: existing binding defined here - --> tests/ui/similar_names.rs:74:9 + --> tests/ui/similar_names.rs:81:9 | LL | let parser: i32; | ^^^^^^ error: binding's name is too similar to existing binding - --> tests/ui/similar_names.rs:102:16 + --> tests/ui/similar_names.rs:112:16 | LL | bpple: sprang, | ^^^^^^ | note: existing binding defined here - --> tests/ui/similar_names.rs:101:16 + --> tests/ui/similar_names.rs:111:16 | LL | apple: spring, | ^^^^^^ -error: aborting due to 5 previous errors +error: aborting due to 6 previous errors diff --git a/tests/ui/unnecessary_cast.fixed b/tests/ui/unnecessary_cast.fixed index c6e9bc3cba07..2b34111c9357 100644 --- a/tests/ui/unnecessary_cast.fixed +++ b/tests/ui/unnecessary_cast.fixed @@ -285,3 +285,11 @@ mod fixable { //~^ unnecessary_cast } } + +fn issue16475() -> *const u8 { + static NONE: Option<((), &'static u8)> = None; + unsafe { + *(&NONE as *const _ as *const _ as *const *const u8) + //~^ unnecessary_cast + } +} diff --git a/tests/ui/unnecessary_cast.rs b/tests/ui/unnecessary_cast.rs index 6936a23d4286..213b6bac3d2b 100644 --- a/tests/ui/unnecessary_cast.rs +++ b/tests/ui/unnecessary_cast.rs @@ -285,3 +285,11 @@ fn issue_14640() { //~^ unnecessary_cast } } + +fn issue16475() -> *const u8 { + static NONE: Option<((), &'static u8)> = None; + unsafe { + *(&NONE as *const _ as *const _ as *const *const u8 as *const *const u8) + //~^ unnecessary_cast + } +} diff --git a/tests/ui/unnecessary_cast.stderr b/tests/ui/unnecessary_cast.stderr index e4f4309ea716..14c14e583134 100644 --- a/tests/ui/unnecessary_cast.stderr +++ b/tests/ui/unnecessary_cast.stderr @@ -277,5 +277,11 @@ error: casting to the same type is unnecessary (`i64` -> `i64`) LL | let _ = 5i32 as i64 as i64; | ^^^^^^^^^^^^^^^^^^ help: try: `5i32 as i64` -error: aborting due to 46 previous errors +error: casting raw pointers to the same type and constness is unnecessary (`*const *const u8` -> `*const *const u8`) + --> tests/ui/unnecessary_cast.rs:292:10 + | +LL | *(&NONE as *const _ as *const _ as *const *const u8 as *const *const u8) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(&NONE as *const _ as *const _ as *const *const u8)` + +error: aborting due to 47 previous errors