From 007c61732677fcd388f9f7887578724d1003f915 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Fri, 24 May 2024 01:33:55 -0700 Subject: [PATCH 01/34] clippy: unnest check_consts --- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 8ee7d87acb3e0..81e94725a70cb 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -6,7 +6,7 @@ use clippy_config::msrvs::{self, Msrv}; use hir::LangItem; use rustc_attr::StableSince; -use rustc_const_eval::transform::check_consts::ConstCx; +use rustc_const_eval::check_consts::ConstCx; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_infer::infer::TyCtxtInferExt; From a456300af569750232afc0e98da28c4786fce19b Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Tue, 20 Feb 2024 20:55:13 -0700 Subject: [PATCH 02/34] Stabilize `LazyCell` and `LazyLock` (`lazy_cell`) --- clippy_dev/src/lib.rs | 1 - src/driver.rs | 1 - tests/compile-test.rs | 1 - tests/dogfood.rs | 1 - tests/lint_message_convention.rs | 1 - tests/workspace.rs | 2 -- 6 files changed, 7 deletions(-) diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 385191e0361b1..3aa43dbe23ed6 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(lazy_cell)] #![feature(let_chains)] #![feature(rustc_private)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] diff --git a/src/driver.rs b/src/driver.rs index 9e42abbc9aa9e..f79da26964f37 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -2,7 +2,6 @@ #![allow(rustc::untranslatable_diagnostic)] #![feature(rustc_private)] #![feature(let_chains)] -#![feature(lazy_cell)] #![feature(lint_reasons)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] // warn on lints, that are included in `rust-lang/rust`s bootstrap diff --git a/tests/compile-test.rs b/tests/compile-test.rs index b06a11702ec86..333a2ab585757 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -1,4 +1,3 @@ -#![feature(lazy_cell)] #![feature(is_sorted)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 3f16c180ea78d..36a7a651c4d28 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -3,7 +3,6 @@ //! //! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context -#![feature(lazy_cell)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index 98019c7552767..6ce7e44474d81 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -1,4 +1,3 @@ -#![feature(lazy_cell)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] diff --git a/tests/workspace.rs b/tests/workspace.rs index 699ab2be199a8..19ccc7ae96070 100644 --- a/tests/workspace.rs +++ b/tests/workspace.rs @@ -1,5 +1,3 @@ -#![feature(lazy_cell)] - use std::path::PathBuf; use std::process::Command; use test_utils::{CARGO_CLIPPY_PATH, IS_RUSTC_TEST_SUITE}; From fc2f7035c67af9ac44ac7c9186323ac903446c47 Mon Sep 17 00:00:00 2001 From: Jakob Schwarz Date: Wed, 15 May 2024 15:07:34 +0200 Subject: [PATCH 03/34] fulfill expectations in `check_unsafe_derive_deserialize` The utility function `clippy_utils::fulfill_or_allowed` is not used because using it would require to move the check for allowed after the check iterating over all inherent impls of the type, doing possibly unnecessary work. Instead, `is_lint_allowed` is called as before, but additionally, once certain that the lint should be emitted, `span_lint_hir_and_then` is called instead of `span_lint_and_help` to also fulfill expectations. fixes: #12802 changelog: fulfill expectations in `check_unsafe_derive_deserialize` --- clippy_lints/src/derive.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 42cd19fb8eca5..b9dd0f638afc4 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; use rustc_errors::Applicability; @@ -390,13 +390,17 @@ fn check_unsafe_derive_deserialize<'tcx>( .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) .any(|imp| has_unsafe(cx, imp)) { - span_lint_and_help( + span_lint_hir_and_then( cx, UNSAFE_DERIVE_DESERIALIZE, + adt_hir_id, item.span, "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", - None, - "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + |diag| { + diag.help( + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + ); + }, ); } } From 1b6c91628eb53e6ddf71ca28b43da2a7659d1056 Mon Sep 17 00:00:00 2001 From: Jakob Schwarz Date: Wed, 15 May 2024 18:21:20 +0200 Subject: [PATCH 04/34] add test for `#[expect]`ing `unsafe_derive_deserialize` Adding `#![feature(lint_reasons)]` to the top of the file also changed the line numbers in the expected error output. --- tests/ui/unsafe_derive_deserialize.rs | 11 +++++++++++ tests/ui/unsafe_derive_deserialize.stderr | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/ui/unsafe_derive_deserialize.rs b/tests/ui/unsafe_derive_deserialize.rs index 70dcaa3afa455..5187e0790423c 100644 --- a/tests/ui/unsafe_derive_deserialize.rs +++ b/tests/ui/unsafe_derive_deserialize.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::unsafe_derive_deserialize)] #![allow(unused, clippy::missing_safety_doc)] @@ -71,4 +72,14 @@ impl G { } } +// Check that we honor the `expect` attribute on the ADT +#[expect(clippy::unsafe_derive_deserialize)] +#[derive(Deserialize)] +pub struct H; +impl H { + pub fn unsafe_block(&self) { + unsafe {} + } +} + fn main() {} diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr index f2d4429f707a5..06719f23d57f3 100644 --- a/tests/ui/unsafe_derive_deserialize.stderr +++ b/tests/ui/unsafe_derive_deserialize.stderr @@ -1,5 +1,5 @@ error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:8:10 + --> tests/ui/unsafe_derive_deserialize.rs:9:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:17:10 + --> tests/ui/unsafe_derive_deserialize.rs:18:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:24:10 + --> tests/ui/unsafe_derive_deserialize.rs:25:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -28,7 +28,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:33:10 + --> tests/ui/unsafe_derive_deserialize.rs:34:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ From 6b371469bf201a3ff83c9343423bbb8c7d6a79fa Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 15 May 2024 11:51:55 -0400 Subject: [PATCH 05/34] Fix tools --- clippy_lints/src/derive.rs | 4 ++-- clippy_utils/src/ty.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 9662c8f4fe2fc..bb6d8b180a0d9 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -11,7 +11,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, ToPredicate, TraitPredicate, Ty, TyCtxt, + self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, Upcast, TraitPredicate, Ty, TyCtxt, }; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; @@ -503,7 +503,7 @@ fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> trait_ref: ty::TraitRef::new(tcx, eq_trait_id, [tcx.mk_param_from_def(param)]), polarity: ty::PredicatePolarity::Positive, }) - .to_predicate(tcx) + .upcast(tcx) }), )), Reveal::UserFacing, diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e3ab42c3107c5..b604f3ff65fc6 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -18,7 +18,7 @@ use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, - GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt, + GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, Upcast, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; @@ -311,7 +311,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( cause: ObligationCause::dummy(), param_env, recursion_depth: 0, - predicate: Binder::dummy(trait_ref).to_predicate(tcx), + predicate: Binder::dummy(trait_ref).upcast(tcx), }; infcx .evaluate_obligation(&obligation) From 4f589e322d20c9ba7cc7e92369faf01d2737825c Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 15 May 2024 12:52:12 -0400 Subject: [PATCH 06/34] Remove trivial Binder::dummy calls --- clippy_utils/src/ty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index b604f3ff65fc6..50c387bd59ec5 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -311,7 +311,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( cause: ObligationCause::dummy(), param_env, recursion_depth: 0, - predicate: Binder::dummy(trait_ref).upcast(tcx), + predicate: trait_ref.upcast(tcx), }; infcx .evaluate_obligation(&obligation) From 0590d71ce2a0dada4f8970ed024f253552902016 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 May 2024 14:17:48 -0300 Subject: [PATCH 07/34] Rename Unsafe to Safety --- clippy_lints/src/derive.rs | 4 ++-- clippy_lints/src/doc/missing_headers.rs | 8 ++++---- clippy_lints/src/doc/mod.rs | 6 +++--- clippy_lints/src/eta_reduction.rs | 12 +++++------- clippy_lints/src/functions/misnamed_getters.rs | 4 ++-- .../src/functions/not_unsafe_ptr_arg_deref.rs | 16 ++++++++-------- clippy_lints/src/inherent_to_string.rs | 4 ++-- clippy_lints/src/methods/mod.rs | 6 ++---- .../src/multiple_unsafe_ops_per_block.rs | 6 +++--- clippy_lints/src/new_without_default.rs | 2 +- clippy_lints/src/ptr.rs | 4 ++-- clippy_lints/src/undocumented_unsafe_blocks.rs | 4 ++-- clippy_utils/src/ast_utils.rs | 16 ++++++++-------- clippy_utils/src/check_proc_macro.rs | 11 +++++------ clippy_utils/src/hir_utils.rs | 2 +- clippy_utils/src/ty.rs | 4 ++-- clippy_utils/src/visitors.rs | 10 +++++----- 17 files changed, 57 insertions(+), 62 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 9662c8f4fe2fc..53e0cef361aa1 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -5,7 +5,7 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, Unsafety, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Safety, Impl, Item, ItemKind, UnsafeSource, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -415,7 +415,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { } if let Some(header) = kind.header() - && header.unsafety == Unsafety::Unsafe + && header.safety == Safety::Unsafe { self.has_unsafe = true; } diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs index f935ae2e3e489..36ba19698c72f 100644 --- a/clippy_lints/src/doc/missing_headers.rs +++ b/clippy_lints/src/doc/missing_headers.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{is_doc_hidden, return_ty}; -use rustc_hir::{BodyId, FnSig, OwnerId, Unsafety}; +use rustc_hir::{BodyId, FnSig, OwnerId, Safety}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{sym, Span}; @@ -33,14 +33,14 @@ pub fn check( } let span = cx.tcx.def_span(owner_id); - match (headers.safety, sig.header.unsafety) { - (false, Unsafety::Unsafe) => span_lint( + match (headers.safety, sig.header.safety) { + (false, Safety::Unsafe) => span_lint( cx, MISSING_SAFETY_DOC, span, "unsafe function's docs miss `# Safety` section", ), - (true, Unsafety::Normal) => span_lint( + (true, Safety::Safe) => span_lint( cx, UNNECESSARY_SAFETY_DOC, span, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 4bced104d3bc9..7fdb582e64033 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -12,7 +12,7 @@ use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options}; use rustc_ast::ast::Attribute; use rustc_data_structures::fx::FxHashSet; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{AnonConst, Expr, ImplItemKind, ItemKind, Node, TraitItemKind, Unsafety}; +use rustc_hir::{AnonConst, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; use rustc_middle::lint::in_external_macro; @@ -415,13 +415,13 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { } }, ItemKind::Trait(_, unsafety, ..) => match (headers.safety, unsafety) { - (false, Unsafety::Unsafe) => span_lint( + (false, Safety::Unsafe) => span_lint( cx, MISSING_SAFETY_DOC, cx.tcx.def_span(item.owner_id), "docs for unsafe trait missing `# Safety` section", ), - (true, Unsafety::Normal) => span_lint( + (true, Safety::Safe) => span_lint( cx, UNNECESSARY_SAFETY_DOC, cx.tcx.def_span(item.owner_id), diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 306a4a9e55c9d..b58018ca0353b 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -5,7 +5,7 @@ use clippy_utils::ty::type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{get_path_from_caller_to_method_type, is_adjusted, path_to_local, path_to_local_id}; use rustc_errors::Applicability; -use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety}; +use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ @@ -146,7 +146,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { ty::FnPtr(sig) => sig.skip_binder(), ty::Closure(_, subs) => cx .tcx - .signature_unclosure(subs.as_closure().sig(), Unsafety::Normal) + .signature_unclosure(subs.as_closure().sig(), Safety::Safe) .skip_binder(), _ => { if typeck.type_dependent_def_id(body.value.hir_id).is_some() @@ -154,7 +154,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction { && let output = typeck.expr_ty(body.value) && let ty::Tuple(tys) = *subs.type_at(1).kind() { - cx.tcx.mk_fn_sig(tys, output, false, Unsafety::Normal, Abi::Rust) + cx.tcx.mk_fn_sig(tys, output, false, Safety::Safe, Abi::Rust) } else { return; } @@ -241,11 +241,9 @@ fn check_inputs( } fn check_sig<'tcx>(cx: &LateContext<'tcx>, closure: ClosureArgs<'tcx>, call_sig: FnSig<'_>) -> bool { - call_sig.unsafety == Unsafety::Normal + call_sig.safety == Safety::Safe && !has_late_bound_to_non_late_bound_regions( - cx.tcx - .signature_unclosure(closure.sig(), Unsafety::Normal) - .skip_binder(), + cx.tcx.signature_unclosure(closure.sig(), Safety::Safe).skip_binder(), call_sig, ) } diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index 8ac17e17688d3..7729c556e1fc8 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind, Unsafety}; +use rustc_hir::{Body, ExprKind, FnDecl, Safety, ImplicitSelfKind}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; @@ -34,7 +34,7 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: ImplicitSelfKind::None => return, }; - let name = if sig.header.unsafety == Unsafety::Unsafe { + let name = if sig.header.safety == Safety::Unsafe { name.strip_suffix("_unchecked").unwrap_or(name) } else { name diff --git a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs index 995dd782cbbd1..b44a5f20ef68e 100644 --- a/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs +++ b/clippy_lints/src/functions/not_unsafe_ptr_arg_deref.rs @@ -19,30 +19,30 @@ pub(super) fn check_fn<'tcx>( body: &'tcx hir::Body<'tcx>, def_id: LocalDefId, ) { - let unsafety = match kind { - intravisit::FnKind::ItemFn(_, _, hir::FnHeader { unsafety, .. }) => unsafety, - intravisit::FnKind::Method(_, sig) => sig.header.unsafety, + let safety = match kind { + intravisit::FnKind::ItemFn(_, _, hir::FnHeader { safety, .. }) => safety, + intravisit::FnKind::Method(_, sig) => sig.header.safety, intravisit::FnKind::Closure => return, }; - check_raw_ptr(cx, unsafety, decl, body, def_id); + check_raw_ptr(cx, safety, decl, body, def_id); } pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { if let hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(eid)) = item.kind { let body = cx.tcx.hir().body(eid); - check_raw_ptr(cx, sig.header.unsafety, sig.decl, body, item.owner_id.def_id); + check_raw_ptr(cx, sig.header.safety, sig.decl, body, item.owner_id.def_id); } } fn check_raw_ptr<'tcx>( cx: &LateContext<'tcx>, - unsafety: hir::Unsafety, + safety: hir::Safety, decl: &'tcx hir::FnDecl<'tcx>, body: &'tcx hir::Body<'tcx>, def_id: LocalDefId, ) { - if unsafety == hir::Unsafety::Normal && cx.effective_visibilities.is_exported(def_id) { + if safety == hir::Safety::Safe && cx.effective_visibilities.is_exported(def_id) { let raw_ptrs = iter_input_pats(decl, body) .filter_map(|arg| raw_ptr_arg(cx, arg)) .collect::(); @@ -58,7 +58,7 @@ fn check_raw_ptr<'tcx>( }, hir::ExprKind::MethodCall(_, recv, args, _) => { let def_id = typeck.type_dependent_def_id(e.hir_id).unwrap(); - if cx.tcx.fn_sig(def_id).skip_binder().skip_binder().unsafety == hir::Unsafety::Unsafe { + if cx.tcx.fn_sig(def_id).skip_binder().skip_binder().safety == hir::Safety::Unsafe { check_arg(cx, &raw_ptrs, recv); for arg in args { check_arg(cx, &raw_ptrs, arg); diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 157f610598478..9aedf5ec7e85f 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::{implements_trait, is_type_lang_item}; use clippy_utils::{return_ty, trait_ref_of_method}; -use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem, Unsafety}; +use rustc_hir::{GenericParamKind, ImplItem, ImplItemKind, LangItem, Safety}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // #11201 && let header = signature.header - && header.unsafety == Unsafety::Normal + && header.safety == Safety::Safe && header.abi == Abi::Rust && impl_item.ident.name == sym::to_string && let decl = signature.decl diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 63545d6c50359..2b92bff016db3 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -5038,7 +5038,7 @@ fn lint_binary_expr_with_method_call(cx: &LateContext<'_>, info: &mut BinaryExpr } const FN_HEADER: hir::FnHeader = hir::FnHeader { - unsafety: hir::Unsafety::Normal, + safety: hir::Safety::Safe, constness: hir::Constness::NotConst, asyncness: hir::IsAsync::NotAsync, abi: rustc_target::spec::abi::Abi::Rust, @@ -5214,7 +5214,5 @@ impl OutType { } fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool { - expected.constness == actual.constness - && expected.unsafety == actual.unsafety - && expected.asyncness == actual.asyncness + expected.constness == actual.constness && expected.safety == actual.safety && expected.asyncness == actual.asyncness } diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 0e13806678059..5306205aed7e8 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable}; use core::ops::ControlFlow::Continue; use hir::def::{DefKind, Res}; -use hir::{BlockCheckMode, ExprKind, QPath, UnOp, Unsafety}; +use hir::{BlockCheckMode, ExprKind, Safety, QPath, UnOp}; use rustc_ast::Mutability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -133,7 +133,7 @@ fn collect_unsafe_exprs<'tcx>( ty::FnPtr(sig) => sig, _ => return Continue(Descend::Yes), }; - if sig.unsafety() == Unsafety::Unsafe { + if sig.safety() == Safety::Unsafe { unsafe_ops.push(("unsafe function call occurs here", expr.span)); } }, @@ -144,7 +144,7 @@ fn collect_unsafe_exprs<'tcx>( .type_dependent_def_id(expr.hir_id) .map(|def_id| cx.tcx.fn_sig(def_id)) { - if sig.skip_binder().unsafety() == Unsafety::Unsafe { + if sig.skip_binder().safety() == Safety::Unsafe { unsafe_ops.push(("unsafe method call occurs here", expr.span)); } } diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 78dd1e051627b..b60fea3f03e0e 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -75,7 +75,7 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind { let name = impl_item.ident.name; let id = impl_item.owner_id; - if sig.header.unsafety == hir::Unsafety::Unsafe { + if sig.header.safety == hir::Safety::Unsafe { // can't be implemented for unsafe new return; } diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 2534e3c8468ce..65929cd5fea9c 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -12,7 +12,7 @@ use rustc_hir::hir_id::{HirId, HirIdMap}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{ self as hir, AnonConst, BinOpKind, BindingMode, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg, ImplItemKind, - ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, TyKind, Unsafety, + ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, Safety, TraitFn, TraitItem, TraitItemKind, TyKind, }; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{Obligation, ObligationCause}; @@ -542,7 +542,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio if let Some(args) = args && !args.is_empty() && body.map_or(true, |body| { - sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, body.value) + sig.header.safety == Safety::Unsafe || contains_unsafe_block(cx, body.value) }) { span_lint_and_then( diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index cbd1618007700..4120bb1331bd0 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -200,7 +200,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { let item_has_safety_comment = item_has_safety_comment(cx, item); match (&item.kind, item_has_safety_comment) { // lint unsafe impl without safety comment - (ItemKind::Impl(impl_), HasSafetyComment::No) if impl_.unsafety == hir::Unsafety::Unsafe => { + (ItemKind::Impl(impl_), HasSafetyComment::No) if impl_.safety == hir::Safety::Unsafe => { if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id()) && !is_unsafe_from_proc_macro(cx, item.span) { @@ -222,7 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { } }, // lint safe impl with unnecessary safety comment - (ItemKind::Impl(impl_), HasSafetyComment::Yes(pos)) if impl_.unsafety == hir::Unsafety::Normal => { + (ItemKind::Impl(impl_), HasSafetyComment::Yes(pos)) if impl_.safety == hir::Safety::Safe => { if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) { let (span, help_span) = mk_spans(pos); diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index d3bbc66bcaea0..d4a5f547211a1 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -386,21 +386,21 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { ( Trait(box ast::Trait { is_auto: la, - unsafety: lu, + safety: lu, generics: lg, bounds: lb, items: li, }), Trait(box ast::Trait { is_auto: ra, - unsafety: ru, + safety: ru, generics: rg, bounds: rb, items: ri, }), ) => { la == ra - && matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No) + && matches!(lu, Safety::Default) == matches!(ru, Safety::Default) && eq_generics(lg, rg) && over(lb, rb, eq_generic_bound) && over(li, ri, |l, r| eq_item(l, r, eq_assoc_item_kind)) @@ -408,7 +408,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { (TraitAlias(lg, lb), TraitAlias(rg, rb)) => eq_generics(lg, rg) && over(lb, rb, eq_generic_bound), ( Impl(box ast::Impl { - unsafety: lu, + safety: lu, polarity: lp, defaultness: ld, constness: lc, @@ -418,7 +418,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { items: li, }), Impl(box ast::Impl { - unsafety: ru, + safety: ru, polarity: rp, defaultness: rd, constness: rc, @@ -428,7 +428,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { items: ri, }), ) => { - matches!(lu, Unsafe::No) == matches!(ru, Unsafe::No) + matches!(lu, Safety::Default) == matches!(ru, Safety::Default) && matches!(lp, ImplPolarity::Positive) == matches!(rp, ImplPolarity::Positive) && eq_defaultness(*ld, *rd) && matches!(lc, ast::Const::No) == matches!(rc, ast::Const::No) @@ -605,7 +605,7 @@ fn eq_opt_coroutine_kind(l: Option, r: Option) -> } pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool { - matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No) + matches!(l.safety, Safety::Default) == matches!(r.safety, Safety::Default) && eq_opt_coroutine_kind(l.coroutine_kind, r.coroutine_kind) && matches!(l.constness, Const::No) == matches!(r.constness, Const::No) && eq_ext(&l.ext, &r.ext) @@ -712,7 +712,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool { both(ll, rl, |l, r| eq_id(l.ident, r.ident)) && l.mutbl == r.mutbl && eq_ty(&l.ty, &r.ty) }, (BareFn(l), BareFn(r)) => { - l.unsafety == r.unsafety + l.safety == r.safety && eq_ext(&l.ext, &r.ext) && over(&l.generic_params, &r.generic_params, eq_generic_param) && eq_fn_decl(&l.decl, &r.decl) diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 422673105136d..553e899997504 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -18,8 +18,8 @@ use rustc_ast::AttrStyle; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl, - ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, MutTy, Node, QPath, TraitItem, - TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Unsafety, Variant, VariantData, YieldSource, + ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, MutTy, Node, QPath, Safety, TraitItem, + TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, }; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::TyCtxt; @@ -207,10 +207,9 @@ fn item_search_pat(item: &Item<'_>) -> (Pat, Pat) { ItemKind::Struct(VariantData::Struct { .. }, _) => (Pat::Str("struct"), Pat::Str("}")), ItemKind::Struct(..) => (Pat::Str("struct"), Pat::Str(";")), ItemKind::Union(..) => (Pat::Str("union"), Pat::Str("}")), - ItemKind::Trait(_, Unsafety::Unsafe, ..) + ItemKind::Trait(_, Safety::Unsafe, ..) | ItemKind::Impl(Impl { - unsafety: Unsafety::Unsafe, - .. + safety: Safety::Unsafe, .. }) => (Pat::Str("unsafe"), Pat::Str("}")), ItemKind::Trait(IsAuto::Yes, ..) => (Pat::Str("auto"), Pat::Str("}")), ItemKind::Trait(..) => (Pat::Str("trait"), Pat::Str("}")), @@ -323,7 +322,7 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::Ptr(MutTy { ty, .. }) => (Pat::Str("*"), ty_search_pat(ty).1), TyKind::Ref(_, MutTy { ty, .. }) => (Pat::Str("&"), ty_search_pat(ty).1), TyKind::BareFn(bare_fn) => ( - if bare_fn.unsafety == Unsafety::Unsafe { + if bare_fn.safety == Safety::Unsafe { Pat::Str("unsafe") } else if bare_fn.abi != Abi::Rust { Pat::Str("extern") diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index c921168df290b..9f285621e0c96 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1082,7 +1082,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { mut_ty.mutbl.hash(&mut self.s); }, TyKind::BareFn(bfn) => { - bfn.unsafety.hash(&mut self.s); + bfn.safety.hash(&mut self.s); bfn.abi.hash(&mut self.s); for arg in bfn.decl.inputs { self.hash_ty(arg); diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e3ab42c3107c5..4f71453d8db80 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, FnDecl, LangItem, TyKind, Unsafety}; +use rustc_hir::{Expr, FnDecl, Safety, LangItem, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::Scalar; @@ -562,7 +562,7 @@ pub fn peel_mid_ty_refs_is_mutable(ty: Ty<'_>) -> (Ty<'_>, usize, Mutability) { /// Returns `true` if the given type is an `unsafe` function. pub fn type_is_unsafe_function<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { match ty.kind() { - ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).unsafety() == Unsafety::Unsafe, + ty::FnDef(..) | ty::FnPtr(_) => ty.fn_sig(cx.tcx).safety() == Safety::Unsafe, _ => false, } } diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index a3f3b32ed372b..90b56297bb556 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -6,7 +6,7 @@ use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor}; use rustc_hir::{ AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, LetExpr, Pat, QPath, - Stmt, UnOp, UnsafeSource, Unsafety, + Safety, Stmt, UnOp, UnsafeSource, }; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; @@ -421,16 +421,16 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { .typeck_results() .type_dependent_def_id(e.hir_id) .map_or(false, |id| { - self.cx.tcx.fn_sig(id).skip_binder().unsafety() == Unsafety::Unsafe + self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe }) => { self.is_unsafe = true; }, ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() { - ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().unsafety() == Unsafety::Unsafe => { + ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe => { self.is_unsafe = true; }, - ty::FnPtr(sig) if sig.unsafety() == Unsafety::Unsafe => self.is_unsafe = true, + ty::FnPtr(sig) if sig.safety() == Safety::Unsafe => self.is_unsafe = true, _ => walk_expr(self, e), }, ExprKind::Path(ref p) @@ -452,7 +452,7 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { } fn visit_nested_item(&mut self, id: ItemId) { if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind { - self.is_unsafe = i.unsafety == Unsafety::Unsafe; + self.is_unsafe = i.safety == Safety::Unsafe; } } } From 0cd64b5756f6834b266ecfdfede47ac66a6359fa Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Thu, 16 May 2024 03:00:48 -0700 Subject: [PATCH 08/34] Fix clippy --- clippy_utils/src/mir/possible_borrower.rs | 4 ++-- clippy_utils/src/qualify_min_const_fn.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 06229ac938f9a..7b4fd8a210edf 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -149,7 +149,7 @@ impl TypeVisitor> for ContainsRegion { } fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { - use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use}; + use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, Repeat, UnaryOp, Use}; let mut visit_op = |op: &mir::Operand<'_>| match op { mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local), @@ -159,7 +159,7 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) { match rvalue { Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op), Aggregate(_, ops) => ops.iter().for_each(visit_op), - BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => { + BinaryOp(_, box (lhs, rhs)) => { visit_op(lhs); visit_op(rhs); }, diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 95851a2eed814..ff9f06531ea11 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -161,7 +161,7 @@ fn check_rvalue<'tcx>( "transmute can attempt to turn pointers into integers, so is unstable in const fn".into(), )), // binops are fine on integers - Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => { + Rvalue::BinaryOp(_, box (lhs, rhs)) => { check_operand(tcx, lhs, span, body)?; check_operand(tcx, rhs, span, body)?; let ty = lhs.ty(body, tcx); From ae547e30009635de6f8d6451cf8e355e5d5207b7 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Sat, 18 May 2024 18:08:11 +0200 Subject: [PATCH 09/34] Fix typos (taking into account review comments) --- clippy_lints/src/methods/needless_collect.rs | 2 +- tests/ui/ptr_arg.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 1c695655536c4..f26f164fa54a4 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -127,7 +127,7 @@ pub(super) fn check<'tcx>( } } -/// checks for for collecting into a (generic) method or function argument +/// checks for collecting into a (generic) method or function argument /// taking an `IntoIterator` fn check_collect_into_intoiterator<'tcx>( cx: &LateContext<'tcx>, diff --git a/tests/ui/ptr_arg.rs b/tests/ui/ptr_arg.rs index fcd716f41444f..5d6e488972cdc 100644 --- a/tests/ui/ptr_arg.rs +++ b/tests/ui/ptr_arg.rs @@ -282,7 +282,7 @@ mod issue_9218 { todo!() } - // These two's return types don't use use 'a so it's not okay + // These two's return types don't use 'a so it's not okay fn cow_bad_ret_ty_1<'a>(input: &'a Cow<'a, str>) -> &'static str { //~^ ERROR: using a reference to `Cow` is not recommended todo!() From 23e8b03f00289643c790bfb618509944f13d6432 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 11 May 2024 11:46:25 +0200 Subject: [PATCH 10/34] Add and use generics.is_empty() and generics.is_own_empty, rather than using generics' attributes --- clippy_lints/src/iter_without_into_iter.rs | 2 +- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index a75dfaf286fdf..601d0e151aae8 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -225,7 +225,7 @@ impl {self_ty_without_ref} {{ && let ImplItemKind::Fn(sig, _) = item.kind && let FnRetTy::Return(ret) = sig.decl.output && is_nameable_in_impl_trait(ret) - && cx.tcx.generics_of(item_did).own_params.is_empty() + && cx.tcx.generics_of(item_did).is_own_empty() && sig.decl.implicit_self == expected_implicit_self && sig.decl.inputs.len() == 1 && let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id()) diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index c2ff19931d5c3..8021930345026 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -176,7 +176,7 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo .get(*lang_item) .map_or(Certainty::Uncertain, |def_id| { let generics = cx.tcx.generics_of(def_id); - if generics.parent_count == 0 && generics.own_params.is_empty() { + if generics.is_empty() { Certainty::Certain(if resolves_to_type { Some(def_id) } else { None }) } else { Certainty::Uncertain From 9d311b5c2b29936349acdc9dfddafc8ea919b51e Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 19 May 2024 21:41:13 +0100 Subject: [PATCH 11/34] initial fix --- .../iter_on_single_or_empty_collections.rs | 43 ++++++++++++++++++- tests/ui/iter_on_empty_collections.fixed | 13 ++++++ tests/ui/iter_on_empty_collections.rs | 13 ++++++ tests/ui/iter_on_empty_collections.stderr | 8 +++- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 6c9bdcff82622..0e929cfe798fd 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,8 +1,12 @@ +use std::iter::once; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core}; use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::HirId; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -25,7 +29,25 @@ impl IterType { } } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) { +fn is_arg_ty_unified_in_fn<'tcx>( + cx: &LateContext<'tcx>, + fn_id: DefId, + arg_id: HirId, + args: impl Iterator> + Clone, +) -> bool { + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); + let arg_id_in_args = args.clone().position(|e| e.hir_id == arg_id).unwrap(); + let arg_ty_in_args = fn_sig.input(arg_id_in_args); + + cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { + clause + .as_projection_clause() + .and_then(|p| p.map_bound(|p| p.term.ty()).transpose()) + .is_some_and(|ty| ty == arg_ty_in_args) + }) +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) { let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), @@ -43,6 +65,25 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { Some((Node::Expr(parent), child_id)) => match parent.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, + ExprKind::Call( + Expr { + kind: ExprKind::Path(path), + hir_id, + .. + }, + args, + ) => is_arg_ty_unified_in_fn( + cx, + cx.typeck_results().qpath_res(path, *hir_id).def_id(), + expr.hir_id, + args.iter(), + ), + ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( + cx, + cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), + expr.hir_id, + once(recv).chain(args.iter()), + ), ExprKind::If(_, _, _) | ExprKind::Match(_, _, _) | ExprKind::Closure(_) diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed index 794629f240eb1..871cc18d7c2d7 100644 --- a/tests/ui/iter_on_empty_collections.fixed +++ b/tests/ui/iter_on_empty_collections.fixed @@ -20,6 +20,19 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain(std::iter::empty()) { + println!("{i}"); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs index a6461a702eb2a..7e3df49eefcd2 100644 --- a/tests/ui/iter_on_empty_collections.rs +++ b/tests/ui/iter_on_empty_collections.rs @@ -20,6 +20,19 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + println!("{i}"); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.stderr b/tests/ui/iter_on_empty_collections.stderr index ade20ff26a019..da9caa6925bd9 100644 --- a/tests/ui/iter_on_empty_collections.stderr +++ b/tests/ui/iter_on_empty_collections.stderr @@ -37,5 +37,11 @@ error: `iter` call on an empty collection LL | assert_eq!(None.iter().next(), Option::<&i32>::None); | ^^^^^^^^^^^ help: try: `std::iter::empty()` -error: aborting due to 6 previous errors +error: `iter` call on an empty collection + --> tests/ui/iter_on_empty_collections.rs:28:66 + | +LL | for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + | ^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 7 previous errors From 36293727238d2559f594d7019a551853cb0c57b3 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 20 May 2024 08:05:49 +0100 Subject: [PATCH 12/34] Lint on closure calls, suppress on callable constants calls --- .../iter_on_single_or_empty_collections.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 0e929cfe798fd..f16385b5f9598 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -33,10 +33,10 @@ fn is_arg_ty_unified_in_fn<'tcx>( cx: &LateContext<'tcx>, fn_id: DefId, arg_id: HirId, - args: impl Iterator> + Clone, + args: impl IntoIterator>, ) -> bool { let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); - let arg_id_in_args = args.clone().position(|e| e.hir_id == arg_id).unwrap(); + let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); let arg_ty_in_args = fn_sig.input(arg_id_in_args); cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { @@ -72,12 +72,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method .. }, args, - ) => is_arg_ty_unified_in_fn( - cx, - cx.typeck_results().qpath_res(path, *hir_id).def_id(), - expr.hir_id, - args.iter(), - ), + ) => { + cx.typeck_results().qpath_res(path, *hir_id).opt_def_id() + .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) + .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, expr.hir_id, args)) + } ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( cx, cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), From 3955bd4c98a61ada7041c028a5d0c749c3c0ee11 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 20 May 2024 08:07:48 +0100 Subject: [PATCH 13/34] fixed formatting --- .../methods/iter_on_single_or_empty_collections.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index f16385b5f9598..e8bba0dfe749c 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -72,11 +72,12 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method .. }, args, - ) => { - cx.typeck_results().qpath_res(path, *hir_id).opt_def_id() - .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) - .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, expr.hir_id, args)) - } + ) => cx + .typeck_results() + .qpath_res(path, *hir_id) + .opt_def_id() + .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) + .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, expr.hir_id, args)), ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( cx, cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), From b31625cdc7034df5fae32c4d6d6ff0e7d43496d8 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Mon, 20 May 2024 21:27:30 +0100 Subject: [PATCH 14/34] Accounted for possible extra layers before the consuming parent expr --- .../src/methods/iter_on_single_or_empty_collections.rs | 4 ++-- tests/ui/iter_on_empty_collections.fixed | 5 +++++ tests/ui/iter_on_empty_collections.rs | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index e8bba0dfe749c..1bf323cf3c7df 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -77,11 +77,11 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method .qpath_res(path, *hir_id) .opt_def_id() .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) - .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, expr.hir_id, args)), + .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)), ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( cx, cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), - expr.hir_id, + child_id, once(recv).chain(args.iter()), ), ExprKind::If(_, _, _) diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed index 871cc18d7c2d7..4b5746c7b6f48 100644 --- a/tests/ui/iter_on_empty_collections.fixed +++ b/tests/ui/iter_on_empty_collections.fixed @@ -29,6 +29,11 @@ fn array() { println!("{i}"); } + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + // Same as above, but for regular function calls for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { println!("{i}"); diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs index 7e3df49eefcd2..737192d556d30 100644 --- a/tests/ui/iter_on_empty_collections.rs +++ b/tests/ui/iter_on_empty_collections.rs @@ -29,6 +29,11 @@ fn array() { println!("{i}"); } + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + // Same as above, but for regular function calls for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { println!("{i}"); From ca1337a7bc08ab59472a701ee0b843d7ac10a5bc Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 17 Apr 2024 19:54:06 -0400 Subject: [PATCH 15/34] Remove a clippy test that doesn't apply anymore --- tests/ui/into_iter_on_ref.fixed | 1 - tests/ui/into_iter_on_ref.rs | 1 - tests/ui/into_iter_on_ref.stderr | 58 ++++++++++++++------------------ 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/tests/ui/into_iter_on_ref.fixed b/tests/ui/into_iter_on_ref.fixed index c03d91c797c8d..72b39c982bf71 100644 --- a/tests/ui/into_iter_on_ref.fixed +++ b/tests/ui/into_iter_on_ref.fixed @@ -11,7 +11,6 @@ fn main() { let _ = vec![1, 2, 3].into_iter(); let _ = (&vec![1, 2, 3]).iter(); //~ ERROR: equivalent to `.iter() - let _ = vec![1, 2, 3].into_boxed_slice().iter(); //~ ERROR: equivalent to `.iter() let _ = std::rc::Rc::from(&[X][..]).iter(); //~ ERROR: equivalent to `.iter() let _ = std::sync::Arc::from(&[X][..]).iter(); //~ ERROR: equivalent to `.iter() diff --git a/tests/ui/into_iter_on_ref.rs b/tests/ui/into_iter_on_ref.rs index 93c732fd6ccf9..5ba224720d340 100644 --- a/tests/ui/into_iter_on_ref.rs +++ b/tests/ui/into_iter_on_ref.rs @@ -11,7 +11,6 @@ fn main() { let _ = vec![1, 2, 3].into_iter(); let _ = (&vec![1, 2, 3]).into_iter(); //~ ERROR: equivalent to `.iter() - let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); //~ ERROR: equivalent to `.iter() let _ = std::rc::Rc::from(&[X][..]).into_iter(); //~ ERROR: equivalent to `.iter() let _ = std::sync::Arc::from(&[X][..]).into_iter(); //~ ERROR: equivalent to `.iter() diff --git a/tests/ui/into_iter_on_ref.stderr b/tests/ui/into_iter_on_ref.stderr index 0e9d485f1a9d9..64d814074da46 100644 --- a/tests/ui/into_iter_on_ref.stderr +++ b/tests/ui/into_iter_on_ref.stderr @@ -8,160 +8,154 @@ LL | let _ = (&vec![1, 2, 3]).into_iter(); = help: to override `-D warnings` add `#[allow(clippy::into_iter_on_ref)]` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` - --> tests/ui/into_iter_on_ref.rs:14:46 - | -LL | let _ = vec![1, 2, 3].into_boxed_slice().into_iter(); - | ^^^^^^^^^ help: call directly: `iter` - -error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` - --> tests/ui/into_iter_on_ref.rs:15:41 + --> tests/ui/into_iter_on_ref.rs:14:41 | LL | let _ = std::rc::Rc::from(&[X][..]).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `slice` - --> tests/ui/into_iter_on_ref.rs:16:44 + --> tests/ui/into_iter_on_ref.rs:15:44 | LL | let _ = std::sync::Arc::from(&[X][..]).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` - --> tests/ui/into_iter_on_ref.rs:18:32 + --> tests/ui/into_iter_on_ref.rs:17:32 | LL | let _ = (&&&&&&&[1, 2, 3]).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` - --> tests/ui/into_iter_on_ref.rs:19:36 + --> tests/ui/into_iter_on_ref.rs:18:36 | LL | let _ = (&&&&mut &&&[1, 2, 3]).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `array` - --> tests/ui/into_iter_on_ref.rs:20:40 + --> tests/ui/into_iter_on_ref.rs:19:40 | LL | let _ = (&mut &mut &mut [1, 2, 3]).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Option` - --> tests/ui/into_iter_on_ref.rs:22:24 + --> tests/ui/into_iter_on_ref.rs:21:24 | LL | let _ = (&Some(4)).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Option` - --> tests/ui/into_iter_on_ref.rs:23:28 + --> tests/ui/into_iter_on_ref.rs:22:28 | LL | let _ = (&mut Some(5)).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Result` - --> tests/ui/into_iter_on_ref.rs:24:32 + --> tests/ui/into_iter_on_ref.rs:23:32 | LL | let _ = (&Ok::<_, i32>(6)).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Result` - --> tests/ui/into_iter_on_ref.rs:25:37 + --> tests/ui/into_iter_on_ref.rs:24:37 | LL | let _ = (&mut Err::(7)).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Vec` - --> tests/ui/into_iter_on_ref.rs:26:34 + --> tests/ui/into_iter_on_ref.rs:25:34 | LL | let _ = (&Vec::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `Vec` - --> tests/ui/into_iter_on_ref.rs:27:38 + --> tests/ui/into_iter_on_ref.rs:26:38 | LL | let _ = (&mut Vec::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeMap` - --> tests/ui/into_iter_on_ref.rs:28:44 + --> tests/ui/into_iter_on_ref.rs:27:44 | LL | let _ = (&BTreeMap::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `BTreeMap` - --> tests/ui/into_iter_on_ref.rs:29:48 + --> tests/ui/into_iter_on_ref.rs:28:48 | LL | let _ = (&mut BTreeMap::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `VecDeque` - --> tests/ui/into_iter_on_ref.rs:30:39 + --> tests/ui/into_iter_on_ref.rs:29:39 | LL | let _ = (&VecDeque::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `VecDeque` - --> tests/ui/into_iter_on_ref.rs:31:43 + --> tests/ui/into_iter_on_ref.rs:30:43 | LL | let _ = (&mut VecDeque::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `LinkedList` - --> tests/ui/into_iter_on_ref.rs:32:41 + --> tests/ui/into_iter_on_ref.rs:31:41 | LL | let _ = (&LinkedList::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `LinkedList` - --> tests/ui/into_iter_on_ref.rs:33:45 + --> tests/ui/into_iter_on_ref.rs:32:45 | LL | let _ = (&mut LinkedList::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `HashMap` - --> tests/ui/into_iter_on_ref.rs:34:43 + --> tests/ui/into_iter_on_ref.rs:33:43 | LL | let _ = (&HashMap::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter_mut()` and will not consume the `HashMap` - --> tests/ui/into_iter_on_ref.rs:35:47 + --> tests/ui/into_iter_on_ref.rs:34:47 | LL | let _ = (&mut HashMap::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter_mut` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeSet` - --> tests/ui/into_iter_on_ref.rs:37:39 + --> tests/ui/into_iter_on_ref.rs:36:39 | LL | let _ = (&BTreeSet::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BinaryHeap` - --> tests/ui/into_iter_on_ref.rs:38:41 + --> tests/ui/into_iter_on_ref.rs:37:41 | LL | let _ = (&BinaryHeap::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `HashSet` - --> tests/ui/into_iter_on_ref.rs:39:38 + --> tests/ui/into_iter_on_ref.rs:38:38 | LL | let _ = (&HashSet::::new()).into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `Path` - --> tests/ui/into_iter_on_ref.rs:40:43 + --> tests/ui/into_iter_on_ref.rs:39:43 | LL | let _ = std::path::Path::new("12/34").into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `PathBuf` - --> tests/ui/into_iter_on_ref.rs:41:47 + --> tests/ui/into_iter_on_ref.rs:40:47 | LL | let _ = std::path::PathBuf::from("12/34").into_iter(); | ^^^^^^^^^ help: call directly: `iter` error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `array` - --> tests/ui/into_iter_on_ref.rs:43:26 + --> tests/ui/into_iter_on_ref.rs:42:26 | LL | let _ = (&[1, 2, 3]).into_iter().next(); | ^^^^^^^^^ help: call directly: `iter` -error: aborting due to 27 previous errors +error: aborting due to 26 previous errors From 4f98cc6d55aa82612fac031a5828f5fc9deee4bc Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 16 May 2024 16:24:58 +0200 Subject: [PATCH 16/34] Correctly handle closing parens in `missing_backticks` doc lint --- clippy_lints/src/doc/markdown.rs | 20 +++++++++++++ tests/ui/doc/issue_12795.fixed | 9 ++++++ tests/ui/doc/issue_12795.rs | 9 ++++++ tests/ui/doc/issue_12795.stderr | 48 ++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 tests/ui/doc/issue_12795.fixed create mode 100644 tests/ui/doc/issue_12795.rs create mode 100644 tests/ui/doc/issue_12795.stderr diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 1add02af31017..20ca88f21160d 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -30,6 +30,7 @@ pub fn check( word = tmp_word; } + let original_len = word.len(); word = word.trim_start_matches(trim_pattern); // Remove leading or trailing single `:` which may be part of a sentence. @@ -44,6 +45,25 @@ pub fn check( continue; } + // Ensure that all reachable matching closing parens are included as well. + let size_diff = original_len - word.len(); + let mut open_parens = 0; + let mut close_parens = 0; + for c in word.chars() { + if c == '(' { + open_parens += 1; + } else if c == ')' { + close_parens += 1; + } + } + while close_parens < open_parens + && let Some(tmp_word) = orig_word.get(size_diff..=(word.len() + size_diff)) + && tmp_word.ends_with(')') + { + word = tmp_word; + close_parens += 1; + } + // Adjust for the current word let offset = word.as_ptr() as usize - text.as_ptr() as usize; let span = Span::new( diff --git a/tests/ui/doc/issue_12795.fixed b/tests/ui/doc/issue_12795.fixed new file mode 100644 index 0000000000000..ade23bf975c2e --- /dev/null +++ b/tests/ui/doc/issue_12795.fixed @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with `a_b(x)` and `a_c` in it and (`a_b((c))` ) too and (maybe `a_b((c))`) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.rs b/tests/ui/doc/issue_12795.rs new file mode 100644 index 0000000000000..6d94a07e30367 --- /dev/null +++ b/tests/ui/doc/issue_12795.rs @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.stderr b/tests/ui/doc/issue_12795.stderr new file mode 100644 index 0000000000000..5700145ec8f7a --- /dev/null +++ b/tests/ui/doc/issue_12795.stderr @@ -0,0 +1,48 @@ +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:20 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_markdown)]` +help: try + | +LL | //! A comment with `a_b(x)` and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:31 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^ + | +help: try + | +LL | //! A comment with a_b(x) and `a_c` in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:46 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (`a_b((c))` ) too and (maybe a_b((c))) + | ~~~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:72 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe `a_b((c))`) + | ~~~~~~~~~~ + +error: aborting due to 4 previous errors + From a14ca6005c991596a8a0ec2bd9b3ccebdf05942e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 21 May 2024 14:07:02 +0200 Subject: [PATCH 17/34] don't inhibit random field reordering on repr(packed(1)) --- clippy_lints/src/transmute/transmute_undefined_repr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/transmute/transmute_undefined_repr.rs b/clippy_lints/src/transmute/transmute_undefined_repr.rs index 9c8dd37d53dcd..3b32e4396b9f1 100644 --- a/clippy_lints/src/transmute/transmute_undefined_repr.rs +++ b/clippy_lints/src/transmute/transmute_undefined_repr.rs @@ -278,7 +278,7 @@ fn reduce_ty<'tcx>(cx: &LateContext<'tcx>, mut ty: Ty<'tcx>) -> ReducedTy<'tcx> ty = sized_ty; continue; } - if def.repr().inhibit_struct_field_reordering_opt() { + if def.repr().inhibit_struct_field_reordering() { ReducedTy::OrderedFields(Some(sized_ty)) } else { ReducedTy::UnorderedFields(ty) From 4363278c73cf506f71215e9e21af90e31c4d74ea Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 21 May 2024 10:39:30 -0700 Subject: [PATCH 18/34] Merge commit '2efebd2f0c03dabbe5c3ad7b4ebfbd99238d1fb2' into clippy-subtree-update --- .github/driver.sh | 19 +- .github/workflows/clippy.yml | 6 +- .github/workflows/clippy_bors.yml | 4 +- CHANGELOG.md | 7 + book/src/lint_configuration.md | 42 ++ clippy_config/src/conf.rs | 31 + clippy_config/src/msrvs.rs | 3 +- clippy_dev/Cargo.toml | 3 +- clippy_dev/src/main.rs | 570 ++++++++---------- clippy_dev/src/new_lint.rs | 14 +- clippy_dev/src/serve.rs | 2 +- clippy_dev/src/update_lints.rs | 4 +- clippy_lints/src/assigning_clones.rs | 37 +- .../src/attrs/duplicated_attributes.rs | 3 +- clippy_lints/src/attrs/mod.rs | 12 +- clippy_lints/src/attrs/useless_attribute.rs | 49 +- .../src/cargo/lint_groups_priority.rs | 13 +- clippy_lints/src/casts/cast_sign_loss.rs | 4 + clippy_lints/src/declared_lints.rs | 4 + clippy_lints/src/doc/lazy_continuation.rs | 95 +++ clippy_lints/src/doc/missing_headers.rs | 9 +- clippy_lints/src/doc/mod.rs | 129 +++- clippy_lints/src/escape.rs | 4 +- clippy_lints/src/explicit_write.rs | 20 +- clippy_lints/src/format.rs | 19 +- clippy_lints/src/format_args.rs | 13 +- clippy_lints/src/format_impl.rs | 10 +- clippy_lints/src/from_str_radix_10.rs | 5 +- clippy_lints/src/functions/mod.rs | 59 +- .../src/functions/renamed_function_params.rs | 110 ++++ clippy_lints/src/future_not_send.rs | 2 +- clippy_lints/src/lib.rs | 55 +- clippy_lints/src/loops/mod.rs | 36 ++ clippy_lints/src/loops/mut_range_bound.rs | 9 +- clippy_lints/src/loops/while_float.rs | 20 + clippy_lints/src/macro_metavars_in_unsafe.rs | 256 ++++++++ clippy_lints/src/manual_clamp.rs | 7 + clippy_lints/src/matches/match_same_arms.rs | 37 +- .../matches/significant_drop_in_scrutinee.rs | 82 +-- clippy_lints/src/methods/expect_fun_call.rs | 7 +- clippy_lints/src/methods/iter_filter.rs | 8 +- clippy_lints/src/methods/iter_kv_map.rs | 2 + .../src/methods/iter_overeager_cloned.rs | 9 +- clippy_lints/src/methods/mod.rs | 15 +- .../src/methods/unnecessary_iter_cloned.rs | 53 +- clippy_lints/src/methods/utils.rs | 1 + .../src/needless_borrows_for_generic_args.rs | 51 +- clippy_lints/src/needless_continue.rs | 3 +- clippy_lints/src/needless_late_init.rs | 69 +-- clippy_lints/src/needless_pass_by_ref_mut.rs | 8 +- clippy_lints/src/needless_pass_by_value.rs | 4 +- clippy_lints/src/no_effect.rs | 8 +- clippy_lints/src/panic_unimplemented.rs | 14 +- clippy_lints/src/strings.rs | 8 + .../transmute/transmute_int_to_non_zero.rs | 2 +- clippy_lints/src/unwrap.rs | 6 +- .../src/utils/format_args_collector.rs | 27 +- clippy_lints/src/write.rs | 14 +- clippy_utils/src/lib.rs | 18 + clippy_utils/src/macros.rs | 78 ++- clippy_utils/src/qualify_min_const_fn.rs | 82 +-- clippy_utils/src/source.rs | 2 +- clippy_utils/src/sugg.rs | 3 +- clippy_utils/src/ty.rs | 15 +- clippy_utils/src/usage.rs | 10 +- lintcheck/Cargo.toml | 2 +- rust-toolchain | 2 +- .../lint_groups_priority/fail/Cargo.stderr | 30 +- .../lint_groups_priority/fail/Cargo.toml | 8 + .../macro_metavars_in_unsafe/default/test.rs | 260 ++++++++ .../default/test.stderr | 187 ++++++ .../private/clippy.toml | 1 + .../macro_metavars_in_unsafe/private/test.rs | 15 + .../private/test.stderr | 17 + tests/ui-toml/panic/clippy.toml | 1 + tests/ui-toml/panic/panic.rs | 54 ++ tests/ui-toml/panic/panic.stderr | 11 + .../default/clippy.toml | 2 + .../extend/clippy.toml | 2 + .../renamed_function_params.default.stderr | 46 ++ .../renamed_function_params.extend.stderr | 34 ++ .../renamed_function_params.rs | 110 ++++ .../conf_disallowed_types.rs | 6 + .../toml_unknown_key/conf_unknown_key.stderr | 9 + tests/ui/assigning_clones.fixed | 26 + tests/ui/assigning_clones.rs | 26 + tests/ui/assigning_clones.stderr | 46 +- tests/ui/doc/doc_lazy_blockquote.fixed | 47 ++ tests/ui/doc/doc_lazy_blockquote.rs | 47 ++ tests/ui/doc/doc_lazy_blockquote.stderr | 76 +++ tests/ui/doc/doc_lazy_list.fixed | 77 +++ tests/ui/doc/doc_lazy_list.rs | 77 +++ tests/ui/doc/doc_lazy_list.stderr | 136 +++++ tests/ui/duplicated_attributes.rs | 6 +- tests/ui/from_str_radix_10.fixed | 11 + tests/ui/from_str_radix_10.rs | 11 + tests/ui/from_str_radix_10.stderr | 16 +- tests/ui/match_same_arms.stderr | 122 ++-- tests/ui/match_same_arms2.fixed | 241 ++++++++ tests/ui/match_same_arms2.rs | 5 +- tests/ui/match_same_arms2.stderr | 257 ++++---- tests/ui/match_same_arms_non_exhaustive.fixed | 61 ++ tests/ui/match_same_arms_non_exhaustive.rs | 1 - .../ui/match_same_arms_non_exhaustive.stderr | 18 +- .../ui/missing_const_for_fn/cant_be_const.rs | 18 +- .../ui/missing_const_for_fn/could_be_const.rs | 28 + .../could_be_const.stderr | 30 +- tests/ui/missing_panics_doc.rs | 8 + .../needless_borrows_for_generic_args.fixed | 42 +- tests/ui/needless_borrows_for_generic_args.rs | 36 +- .../needless_borrows_for_generic_args.stderr | 26 +- tests/ui/needless_late_init.stderr | 177 +++--- tests/ui/no_effect.rs | 13 - tests/ui/no_effect.stderr | 68 +-- tests/ui/significant_drop_in_scrutinee.rs | 56 ++ tests/ui/significant_drop_in_scrutinee.stderr | 28 +- tests/ui/unnecessary_iter_cloned.fixed | 32 + tests/ui/unnecessary_iter_cloned.rs | 32 + tests/ui/unnecessary_iter_cloned.stderr | 4 +- tests/ui/useless_attribute.fixed | 43 ++ tests/ui/useless_attribute.rs | 43 ++ tests/ui/while_float.rs | 14 + tests/ui/while_float.stderr | 20 + util/gh-pages/script.js | 2 +- 124 files changed, 4025 insertions(+), 1120 deletions(-) create mode 100644 clippy_lints/src/doc/lazy_continuation.rs create mode 100644 clippy_lints/src/functions/renamed_function_params.rs create mode 100644 clippy_lints/src/loops/while_float.rs create mode 100644 clippy_lints/src/macro_metavars_in_unsafe.rs create mode 100644 tests/ui-toml/macro_metavars_in_unsafe/default/test.rs create mode 100644 tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr create mode 100644 tests/ui-toml/macro_metavars_in_unsafe/private/clippy.toml create mode 100644 tests/ui-toml/macro_metavars_in_unsafe/private/test.rs create mode 100644 tests/ui-toml/macro_metavars_in_unsafe/private/test.stderr create mode 100644 tests/ui-toml/panic/clippy.toml create mode 100644 tests/ui-toml/panic/panic.rs create mode 100644 tests/ui-toml/panic/panic.stderr create mode 100644 tests/ui-toml/renamed_function_params/default/clippy.toml create mode 100644 tests/ui-toml/renamed_function_params/extend/clippy.toml create mode 100644 tests/ui-toml/renamed_function_params/renamed_function_params.default.stderr create mode 100644 tests/ui-toml/renamed_function_params/renamed_function_params.extend.stderr create mode 100644 tests/ui-toml/renamed_function_params/renamed_function_params.rs create mode 100644 tests/ui/doc/doc_lazy_blockquote.fixed create mode 100644 tests/ui/doc/doc_lazy_blockquote.rs create mode 100644 tests/ui/doc/doc_lazy_blockquote.stderr create mode 100644 tests/ui/doc/doc_lazy_list.fixed create mode 100644 tests/ui/doc/doc_lazy_list.rs create mode 100644 tests/ui/doc/doc_lazy_list.stderr create mode 100644 tests/ui/match_same_arms2.fixed create mode 100644 tests/ui/match_same_arms_non_exhaustive.fixed create mode 100644 tests/ui/while_float.rs create mode 100644 tests/ui/while_float.stderr diff --git a/.github/driver.sh b/.github/driver.sh index 2eafdd0fbc87b..09202b1878b23 100755 --- a/.github/driver.sh +++ b/.github/driver.sh @@ -2,15 +2,18 @@ set -ex +sysroot="$(rustc --print sysroot)" +case $OS in + Linux) export LD_LIBRARY_PATH="$sysroot/lib" ;; + macOS) export DYLD_FALLBACK_LIBRARY_PATH="$sysroot/lib" ;; + Windows) export PATH="$(cygpath "$sysroot")/bin:$PATH" ;; + *) exit 1 +esac + # Check sysroot handling -sysroot=$(./target/debug/clippy-driver --print sysroot) -test "$sysroot" = "$(rustc --print sysroot)" - -if [[ ${OS} == "Windows" ]]; then - desired_sysroot=C:/tmp -else - desired_sysroot=/tmp -fi +test "$(./target/debug/clippy-driver --print sysroot)" = "$sysroot" + +desired_sysroot="target/sysroot" # Set --sysroot in command line sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot) test "$sysroot" = $desired_sysroot diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 8179e3e65b541..06bf3b6fdbfab 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -69,6 +69,6 @@ jobs: working-directory: clippy_dev - name: Test clippy-driver - run: | - TOOLCHAIN=$(rustup show active-toolchain | cut -f1 -d' ') - rustup run $TOOLCHAIN bash .github/driver.sh + run: .github/driver.sh + env: + OS: ${{ runner.os }} diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 94515987eba4a..1f4bec9291823 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -116,9 +116,7 @@ jobs: working-directory: clippy_dev - name: Test clippy-driver - run: | - TOOLCHAIN=$(rustup show active-toolchain | cut -f1 -d' ') - rustup run $TOOLCHAIN bash .github/driver.sh + run: .github/driver.sh env: OS: ${{ runner.os }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c9ea11408143..d5115f70f6620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5249,6 +5249,7 @@ Released 2018-09-13 [`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type [`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression +[`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation [`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons @@ -5447,6 +5448,7 @@ Released 2018-09-13 [`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes [`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug [`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal +[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe [`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports [`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion [`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert @@ -5702,6 +5704,7 @@ Released 2018-09-13 [`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref [`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns [`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro +[`renamed_function_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params [`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once [`repeat_vec_with_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity [`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts @@ -5908,6 +5911,7 @@ Released 2018-09-13 [`verbose_file_reads`]: https://rust-lang.github.io/rust-clippy/master/index.html#verbose_file_reads [`vtable_address_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons [`waker_clone_wake`]: https://rust-lang.github.io/rust-clippy/master/index.html#waker_clone_wake +[`while_float`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_float [`while_immutable_condition`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_immutable_condition [`while_let_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_loop [`while_let_on_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#while_let_on_iterator @@ -5939,8 +5943,10 @@ Released 2018-09-13 [`allow-expect-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-expect-in-tests [`allow-mixed-uninlined-format-args`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-mixed-uninlined-format-args [`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings +[`allow-panic-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-panic-in-tests [`allow-print-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-print-in-tests [`allow-private-module-inception`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-private-module-inception +[`allow-renamed-params-for`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-renamed-params-for [`allow-unwrap-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-unwrap-in-tests [`allow-useless-vec-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-useless-vec-in-tests [`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles @@ -6002,4 +6008,5 @@ Released 2018-09-13 [`vec-box-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#vec-box-size-threshold [`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold [`warn-on-all-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-on-all-wildcard-imports +[`warn-unsafe-macro-metavars-in-private-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-unsafe-macro-metavars-in-private-macros diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index f6af9810ca166..c8223007df7b3 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -101,6 +101,16 @@ Whether to allow `r#""#` when `r""` can be used * [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes) +## `allow-panic-in-tests` +Whether `panic` should be allowed in test functions or `#[cfg(test)]` + +**Default Value:** `false` + +--- +**Affected lints:** +* [`panic`](https://rust-lang.github.io/rust-clippy/master/index.html#panic) + + ## `allow-print-in-tests` Whether print macros (ex. `println!`) should be allowed in test functions or `#[cfg(test)]` @@ -122,6 +132,28 @@ Whether to allow module inception if it's not public. * [`module_inception`](https://rust-lang.github.io/rust-clippy/master/index.html#module_inception) +## `allow-renamed-params-for` +List of trait paths to ignore when checking renamed function parameters. + +#### Example + +```toml +allow-renamed-params-for = [ "std::convert::From" ] +``` + +#### Noteworthy + +- By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` +- `".."` can be used as part of the list to indicate that the configured values should be appended to the +default configuration of Clippy. By default, any configuration will replace the default value. + +**Default Value:** `["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]` + +--- +**Affected lints:** +* [`renamed_function_params`](https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params) + + ## `allow-unwrap-in-tests` Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` @@ -900,3 +932,13 @@ Whether to allow certain wildcard imports (prelude, super in tests). * [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports) +## `warn-unsafe-macro-metavars-in-private-macros` +Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros. + +**Default Value:** `false` + +--- +**Affected lints:** +* [`macro_metavars_in_unsafe`](https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe) + + diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 5cfcbdb57d735..cfdf620b7d074 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -40,6 +40,8 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"]; const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"]; const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"]; +const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] = + &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]; /// Conf with parse errors #[derive(Default)] @@ -455,6 +457,10 @@ define_Conf! { /// /// Whether `unwrap` should be allowed in test functions or `#[cfg(test)]` (allow_unwrap_in_tests: bool = false), + /// Lint: PANIC. + /// + /// Whether `panic` should be allowed in test functions or `#[cfg(test)]` + (allow_panic_in_tests: bool = false), /// Lint: DBG_MACRO. /// /// Whether `dbg!` should be allowed in test functions or `#[cfg(test)]` @@ -613,6 +619,27 @@ define_Conf! { /// - Use `".."` as part of the list to indicate that the configured values should be appended to the /// default configuration of Clippy. By default, any configuration will replace the default value (allowed_prefixes: Vec = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()), + /// Lint: RENAMED_FUNCTION_PARAMS. + /// + /// List of trait paths to ignore when checking renamed function parameters. + /// + /// #### Example + /// + /// ```toml + /// allow-renamed-params-for = [ "std::convert::From" ] + /// ``` + /// + /// #### Noteworthy + /// + /// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr` + /// - `".."` can be used as part of the list to indicate that the configured values should be appended to the + /// default configuration of Clippy. By default, any configuration will replace the default value. + (allow_renamed_params_for: Vec = + DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect()), + /// Lint: MACRO_METAVARS_IN_UNSAFE. + /// + /// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros. + (warn_unsafe_macro_metavars_in_private_macros: bool = false), } /// Search for the configuration file. @@ -674,6 +701,10 @@ fn deserialize(file: &SourceFile) -> TryConf { extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES); + extend_vec_if_indicator_present( + &mut conf.conf.allow_renamed_params_for, + DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS, + ); // TODO: THIS SHOULD BE TESTED, this comment will be gone soon if conf.conf.allowed_idents_below_min_chars.contains("..") { conf.conf diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 14808440d48d3..a3e7d0c3fa5fe 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -26,7 +26,8 @@ msrv_aliases! { 1,63,0 { CLONE_INTO } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } 1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST } - 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY } + 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } + 1,56,0 { CONST_FN_UNION } 1,55,0 { SEEK_REWIND } 1,54,0 { INTO_KEYS } 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR } diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index 42a953039b1c0..4104e7d94f146 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "clippy_dev" +description = "Clippy developer tooling" version = "0.0.1" edition = "2021" [dependencies] aho-corasick = "1.0" -clap = "4.1.4" +clap = { version = "4.4", features = ["derive"] } indoc = "1.0" itertools = "0.12" opener = "0.6" diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 397a0e990829c..366b52b25dfcf 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -2,350 +2,292 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use clap::{Arg, ArgAction, ArgMatches, Command}; +use clap::{Args, Parser, Subcommand}; use clippy_dev::{dogfood, fmt, lint, new_lint, serve, setup, update_lints}; -use indoc::indoc; use std::convert::Infallible; fn main() { - let matches = get_clap_config(); + let dev = Dev::parse(); - match matches.subcommand() { - Some(("bless", _)) => { + match dev.command { + DevCommand::Bless => { eprintln!("use `cargo bless` to automatically replace `.stderr` and `.fixed` files as tests are being run"); }, - Some(("dogfood", matches)) => { - dogfood::dogfood( - matches.get_flag("fix"), - matches.get_flag("allow-dirty"), - matches.get_flag("allow-staged"), - ); - }, - Some(("fmt", matches)) => { - fmt::run(matches.get_flag("check"), matches.get_flag("verbose")); - }, - Some(("update_lints", matches)) => { - if matches.get_flag("print-only") { + DevCommand::Dogfood { + fix, + allow_dirty, + allow_staged, + } => dogfood::dogfood(fix, allow_dirty, allow_staged), + DevCommand::Fmt { check, verbose } => fmt::run(check, verbose), + DevCommand::UpdateLints { print_only, check } => { + if print_only { update_lints::print_lints(); - } else if matches.get_flag("check") { + } else if check { update_lints::update(update_lints::UpdateMode::Check); } else { update_lints::update(update_lints::UpdateMode::Change); } }, - Some(("new_lint", matches)) => { - match new_lint::create( - matches.get_one::("pass").unwrap(), - matches.get_one::("name"), - matches.get_one::("category").map(String::as_str), - matches.get_one::("type").map(String::as_str), - matches.get_flag("msrv"), - ) { - Ok(()) => update_lints::update(update_lints::UpdateMode::Change), - Err(e) => eprintln!("Unable to create lint: {e}"), - } + DevCommand::NewLint { + pass, + name, + category, + r#type, + msrv, + } => match new_lint::create(&pass, &name, &category, r#type.as_deref(), msrv) { + Ok(()) => update_lints::update(update_lints::UpdateMode::Change), + Err(e) => eprintln!("Unable to create lint: {e}"), }, - Some(("setup", sub_command)) => match sub_command.subcommand() { - Some(("git-hook", matches)) => { - if matches.get_flag("remove") { - setup::git_hook::remove_hook(); + DevCommand::Setup(SetupCommand { subcommand }) => match subcommand { + SetupSubcommand::Intellij { remove, repo_path } => { + if remove { + setup::intellij::remove_rustc_src(); } else { - setup::git_hook::install_hook(matches.get_flag("force-override")); + setup::intellij::setup_rustc_src(&repo_path); } }, - Some(("intellij", matches)) => { - if matches.get_flag("remove") { - setup::intellij::remove_rustc_src(); + SetupSubcommand::GitHook { remove, force_override } => { + if remove { + setup::git_hook::remove_hook(); } else { - setup::intellij::setup_rustc_src( - matches - .get_one::("rustc-repo-path") - .expect("this field is mandatory and therefore always valid"), - ); + setup::git_hook::install_hook(force_override); } }, - Some(("toolchain", matches)) => { - setup::toolchain::create( - matches.get_flag("force"), - matches.get_flag("release"), - matches.get_one::("name").unwrap(), - ); - }, - Some(("vscode-tasks", matches)) => { - if matches.get_flag("remove") { + SetupSubcommand::Toolchain { force, release, name } => setup::toolchain::create(force, release, &name), + SetupSubcommand::VscodeTasks { remove, force_override } => { + if remove { setup::vscode::remove_tasks(); } else { - setup::vscode::install_tasks(matches.get_flag("force-override")); + setup::vscode::install_tasks(force_override); } }, - _ => {}, - }, - Some(("remove", sub_command)) => match sub_command.subcommand() { - Some(("git-hook", _)) => setup::git_hook::remove_hook(), - Some(("intellij", _)) => setup::intellij::remove_rustc_src(), - Some(("vscode-tasks", _)) => setup::vscode::remove_tasks(), - _ => {}, - }, - Some(("serve", matches)) => { - let port = *matches.get_one::("port").unwrap(); - let lint = matches.get_one::("lint"); - serve::run(port, lint); }, - Some(("lint", matches)) => { - let path = matches.get_one::("path").unwrap(); - let args = matches.get_many::("args").into_iter().flatten(); - lint::run(path, args); + DevCommand::Remove(RemoveCommand { subcommand }) => match subcommand { + RemoveSubcommand::Intellij => setup::intellij::remove_rustc_src(), + RemoveSubcommand::GitHook => setup::git_hook::remove_hook(), + RemoveSubcommand::VscodeTasks => setup::vscode::remove_tasks(), }, - Some(("rename_lint", matches)) => { - let old_name = matches.get_one::("old_name").unwrap(); - let new_name = matches.get_one::("new_name").unwrap_or(old_name); - let uplift = matches.get_flag("uplift"); - update_lints::rename(old_name, new_name, uplift); - }, - Some(("deprecate", matches)) => { - let name = matches.get_one::("name").unwrap(); - let reason = matches.get_one("reason"); - update_lints::deprecate(name, reason); - }, - _ => {}, + DevCommand::Serve { port, lint } => serve::run(port, lint), + DevCommand::Lint { path, args } => lint::run(&path, args.iter()), + DevCommand::RenameLint { + old_name, + new_name, + uplift, + } => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift), + DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()), } } -fn get_clap_config() -> ArgMatches { - Command::new("Clippy developer tooling") - .arg_required_else_help(true) - .subcommands([ - Command::new("bless").about("bless the test output changes").arg( - Arg::new("ignore-timestamp") - .long("ignore-timestamp") - .action(ArgAction::SetTrue) - .help("Include files updated before clippy was built"), - ), - Command::new("dogfood").about("Runs the dogfood test").args([ - Arg::new("fix") - .long("fix") - .action(ArgAction::SetTrue) - .help("Apply the suggestions when possible"), - Arg::new("allow-dirty") - .long("allow-dirty") - .action(ArgAction::SetTrue) - .help("Fix code even if the working directory has changes") - .requires("fix"), - Arg::new("allow-staged") - .long("allow-staged") - .action(ArgAction::SetTrue) - .help("Fix code even if the working directory has staged changes") - .requires("fix"), - ]), - Command::new("fmt") - .about("Run rustfmt on all projects and tests") - .args([ - Arg::new("check") - .long("check") - .action(ArgAction::SetTrue) - .help("Use the rustfmt --check option"), - Arg::new("verbose") - .short('v') - .long("verbose") - .action(ArgAction::SetTrue) - .help("Echo commands run"), - ]), - Command::new("update_lints") - .about("Updates lint registration and information from the source code") - .long_about( - "Makes sure that:\n \ - * the lint count in README.md is correct\n \ - * the changelog contains markdown link references at the bottom\n \ - * all lint groups include the correct lints\n \ - * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ - * all lints are registered in the lint store", - ) - .args([ - Arg::new("print-only") - .long("print-only") - .action(ArgAction::SetTrue) - .help( - "Print a table of lints to STDOUT. \ - This does not include deprecated and internal lints. \ - (Does not modify any files)", - ), - Arg::new("check") - .long("check") - .action(ArgAction::SetTrue) - .help("Checks that `cargo dev update_lints` has been run. Used on CI."), - ]), - Command::new("new_lint") - .about("Create new lint and run `cargo dev update_lints`") - .args([ - Arg::new("pass") - .short('p') - .long("pass") - .help("Specify whether the lint runs during the early or late pass") - .value_parser(["early", "late"]) - .conflicts_with("type") - .default_value("late"), - Arg::new("name") - .short('n') - .long("name") - .help("Name of the new lint in snake case, ex: fn_too_long") - .required(true) - .value_parser(|name: &str| Ok::<_, Infallible>(name.replace('-', "_"))), - Arg::new("category") - .short('c') - .long("category") - .help("What category the lint belongs to") - .default_value("nursery") - .value_parser([ - "style", - "correctness", - "suspicious", - "complexity", - "perf", - "pedantic", - "restriction", - "cargo", - "nursery", - "internal", - ]), - Arg::new("type").long("type").help("What directory the lint belongs in"), - Arg::new("msrv") - .long("msrv") - .action(ArgAction::SetTrue) - .help("Add MSRV config code to the lint"), - ]), - Command::new("setup") - .about("Support for setting up your personal development environment") - .arg_required_else_help(true) - .subcommands([ - Command::new("git-hook") - .about("Add a pre-commit git hook that formats your code to make it look pretty") - .args([ - Arg::new("remove") - .long("remove") - .action(ArgAction::SetTrue) - .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'"), - Arg::new("force-override") - .long("force-override") - .short('f') - .action(ArgAction::SetTrue) - .help("Forces the override of an existing git pre-commit hook"), - ]), - Command::new("intellij") - .about("Alter dependencies so Intellij Rust can find rustc internals") - .args([ - Arg::new("remove") - .long("remove") - .action(ArgAction::SetTrue) - .help("Remove the dependencies added with 'cargo dev setup intellij'"), - Arg::new("rustc-repo-path") - .long("repo-path") - .short('r') - .help("The path to a rustc repo that will be used for setting the dependencies") - .value_name("path") - .conflicts_with("remove") - .required(true), - ]), - Command::new("toolchain") - .about("Install a rustup toolchain pointing to the local clippy build") - .args([ - Arg::new("force") - .long("force") - .short('f') - .action(ArgAction::SetTrue) - .help("Override an existing toolchain"), - Arg::new("release") - .long("release") - .short('r') - .action(ArgAction::SetTrue) - .help("Point to --release clippy binaries"), - Arg::new("name") - .long("name") - .default_value("clippy") - .help("The name of the created toolchain"), - ]), - Command::new("vscode-tasks") - .about("Add several tasks to vscode for formatting, validation and testing") - .args([ - Arg::new("remove") - .long("remove") - .action(ArgAction::SetTrue) - .help("Remove the tasks added with 'cargo dev setup vscode-tasks'"), - Arg::new("force-override") - .long("force-override") - .short('f') - .action(ArgAction::SetTrue) - .help("Forces the override of existing vscode tasks"), - ]), - ]), - Command::new("remove") - .about("Support for undoing changes done by the setup command") - .arg_required_else_help(true) - .subcommands([ - Command::new("git-hook").about("Remove any existing pre-commit git hook"), - Command::new("vscode-tasks").about("Remove any existing vscode tasks"), - Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"), - ]), - Command::new("serve") - .about("Launch a local 'ALL the Clippy Lints' website in a browser") - .args([ - Arg::new("port") - .long("port") - .short('p') - .help("Local port for the http server") - .default_value("8000") - .value_parser(clap::value_parser!(u16)), - Arg::new("lint").help("Which lint's page to load initially (optional)"), - ]), - Command::new("lint") - .about("Manually run clippy on a file or package") - .after_help(indoc! {" - EXAMPLES - Lint a single file: - cargo dev lint tests/ui/attrs.rs +#[derive(Parser)] +#[command(name = "dev", about)] +struct Dev { + #[command(subcommand)] + command: DevCommand, +} - Lint a package directory: - cargo dev lint tests/ui-cargo/wildcard_dependencies/fail - cargo dev lint ~/my-project +#[derive(Subcommand)] +enum DevCommand { + /// Bless the test output changes + Bless, + /// Runs the dogfood test + Dogfood { + #[arg(long)] + /// Apply the suggestions when possible + fix: bool, + #[arg(long, requires = "fix")] + /// Fix code even if the working directory has changes + allow_dirty: bool, + #[arg(long, requires = "fix")] + /// Fix code even if the working directory has staged changes + allow_staged: bool, + }, + /// Run rustfmt on all projects and tests + Fmt { + #[arg(long)] + /// Use the rustfmt --check option + check: bool, + #[arg(short, long)] + /// Echo commands run + verbose: bool, + }, + #[command(name = "update_lints")] + /// Updates lint registration and information from the source code + /// + /// Makes sure that: {n} + /// * the lint count in README.md is correct {n} + /// * the changelog contains markdown link references at the bottom {n} + /// * all lint groups include the correct lints {n} + /// * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod` {n} + /// * all lints are registered in the lint store + UpdateLints { + #[arg(long)] + /// Print a table of lints to STDOUT + /// + /// This does not include deprecated and internal lints. (Does not modify any files) + print_only: bool, + #[arg(long)] + /// Checks that `cargo dev update_lints` has been run. Used on CI. + check: bool, + }, + #[command(name = "new_lint")] + /// Create a new lint and run `cargo dev update_lints` + NewLint { + #[arg(short, long, value_parser = ["early", "late"], conflicts_with = "type", default_value = "late")] + /// Specify whether the lint runs during the early or late pass + pass: String, + #[arg( + short, + long, + value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")), + )] + /// Name of the new lint in snake case, ex: `fn_too_long` + name: String, + #[arg( + short, + long, + value_parser = [ + "style", + "correctness", + "suspicious", + "complexity", + "perf", + "pedantic", + "restriction", + "cargo", + "nursery", + "internal", + ], + default_value = "nursery", + )] + /// What category the lint belongs to + category: String, + #[arg(long)] + /// What directory the lint belongs in + r#type: Option, + #[arg(long)] + /// Add MSRV config code to the lint + msrv: bool, + }, + /// Support for setting up your personal development environment + Setup(SetupCommand), + /// Support for removing changes done by the setup command + Remove(RemoveCommand), + /// Launch a local 'ALL the Clippy Lints' website in a browser + Serve { + #[arg(short, long, default_value = "8000")] + /// Local port for the http server + port: u16, + #[arg(long)] + /// Which lint's page to load initially (optional) + lint: Option, + }, + #[allow(clippy::doc_markdown)] + /// Manually run clippy on a file or package + /// + /// ## Examples + /// + /// Lint a single file: {n} + /// cargo dev lint tests/ui/attrs.rs + /// + /// Lint a package directory: {n} + /// cargo dev lint tests/ui-cargo/wildcard_dependencies/fail {n} + /// cargo dev lint ~/my-project + /// + /// Run rustfix: {n} + /// cargo dev lint ~/my-project -- --fix + /// + /// Set lint levels: {n} + /// cargo dev lint file.rs -- -W clippy::pedantic {n} + /// cargo dev lint ~/my-project -- -- -W clippy::pedantic + Lint { + /// The path to a file or package directory to lint + path: String, + /// Pass extra arguments to cargo/clippy-driver + args: Vec, + }, + #[command(name = "rename_lint")] + /// Rename a lint + RenameLint { + /// The name of the lint to rename + old_name: String, + #[arg(required_unless_present = "uplift")] + /// The new name of the lint + new_name: Option, + #[arg(long)] + /// This lint will be uplifted into rustc + uplift: bool, + }, + /// Deprecate the given lint + Deprecate { + /// The name of the lint to deprecate + name: String, + #[arg(long, short)] + /// The reason for deprecation + reason: Option, + }, +} - Run rustfix: - cargo dev lint ~/my-project -- --fix +#[derive(Args)] +struct SetupCommand { + #[command(subcommand)] + subcommand: SetupSubcommand, +} + +#[derive(Subcommand)] +enum SetupSubcommand { + /// Alter dependencies so Intellij Rust can find rustc internals + Intellij { + #[arg(long)] + /// Remove the dependencies added with 'cargo dev setup intellij' + remove: bool, + #[arg(long, short, conflicts_with = "remove")] + /// The path to a rustc repo that will be used for setting the dependencies + repo_path: String, + }, + /// Add a pre-commit git hook that formats your code to make it look pretty + GitHook { + #[arg(long)] + /// Remove the pre-commit hook added with 'cargo dev setup git-hook' + remove: bool, + #[arg(long, short)] + /// Forces the override of an existing git pre-commit hook + force_override: bool, + }, + /// Install a rustup toolchain pointing to the local clippy build + Toolchain { + #[arg(long, short)] + /// Override an existing toolchain + force: bool, + #[arg(long, short)] + /// Point to --release clippy binary + release: bool, + #[arg(long, default_value = "clippy")] + /// Name of the toolchain + name: String, + }, + /// Add several tasks to vscode for formatting, validation and testing + VscodeTasks { + #[arg(long)] + /// Remove the tasks added with 'cargo dev setup vscode-tasks' + remove: bool, + #[arg(long, short)] + /// Forces the override of existing vscode tasks + force_override: bool, + }, +} + +#[derive(Args)] +struct RemoveCommand { + #[command(subcommand)] + subcommand: RemoveSubcommand, +} - Set lint levels: - cargo dev lint file.rs -- -W clippy::pedantic - cargo dev lint ~/my-project -- -- -W clippy::pedantic - "}) - .args([ - Arg::new("path") - .required(true) - .help("The path to a file or package directory to lint"), - Arg::new("args") - .action(ArgAction::Append) - .help("Pass extra arguments to cargo/clippy-driver"), - ]), - Command::new("rename_lint").about("Renames the given lint").args([ - Arg::new("old_name") - .index(1) - .required(true) - .help("The name of the lint to rename"), - Arg::new("new_name") - .index(2) - .required_unless_present("uplift") - .help("The new name of the lint"), - Arg::new("uplift") - .long("uplift") - .action(ArgAction::SetTrue) - .help("This lint will be uplifted into rustc"), - ]), - Command::new("deprecate").about("Deprecates the given lint").args([ - Arg::new("name") - .index(1) - .required(true) - .help("The name of the lint to deprecate"), - Arg::new("reason") - .long("reason") - .short('r') - .help("The reason for deprecation"), - ]), - ]) - .get_matches() +#[derive(Subcommand)] +enum RemoveSubcommand { + /// Remove the dependencies added with 'cargo dev setup intellij' + Intellij, + /// Remove the pre-commit git hook + GitHook, + /// Remove the tasks added with 'cargo dev setup vscode-tasks' + VscodeTasks, } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 2940d56350f41..b6481dde4dde0 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -36,22 +36,16 @@ impl Context for io::Result { /// # Errors /// /// This function errors out if the files couldn't be created or written to. -pub fn create( - pass: &String, - lint_name: Option<&String>, - category: Option<&str>, - mut ty: Option<&str>, - msrv: bool, -) -> io::Result<()> { - if category == Some("cargo") && ty.is_none() { +pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> { + if category == "cargo" && ty.is_none() { // `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo` ty = Some("cargo"); } let lint = LintData { pass, - name: lint_name.expect("`name` argument is validated by clap"), - category: category.expect("`category` argument is validated by clap"), + name, + category, ty, project_root: clippy_project_root(), }; diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index ea925f6709f9b..4a4261d1a1e63 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -8,7 +8,7 @@ use std::{env, thread}; /// # Panics /// /// Panics if the python commands could not be spawned -pub fn run(port: u16, lint: Option<&String>) -> ! { +pub fn run(port: u16, lint: Option) -> ! { let mut url = Some(match lint { None => format!("http://localhost:{port}"), Some(lint) => format!("http://localhost:{port}/#{lint}"), diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 625b133959133..45353901c98fd 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -314,7 +314,7 @@ const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note"; /// # Panics /// /// If a file path could not read from or written to -pub fn deprecate(name: &str, reason: Option<&String>) { +pub fn deprecate(name: &str, reason: Option<&str>) { fn finish( (lints, mut deprecated_lints, renamed_lints): (Vec, Vec, Vec), name: &str, @@ -335,7 +335,7 @@ pub fn deprecate(name: &str, reason: Option<&String>) { println!("note: you must run `cargo uitest` to update the test results"); } - let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str); + let reason = reason.unwrap_or(DEFAULT_DEPRECATION_REASON); let name_lower = name.to_lowercase(); let name_upper = name.to_uppercase(); diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index f0dafb1ae0d52..e94a6f3e3fc5c 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -2,15 +2,15 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::HirNode; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_trait_method, path_to_local}; +use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Expr, ExprKind, Node}; +use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Instance, Mutability}; use rustc_session::impl_lint_pass; use rustc_span::def_id::DefId; use rustc_span::symbol::sym; -use rustc_span::ExpnKind; +use rustc_span::{ExpnKind, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -36,6 +36,7 @@ declare_clippy_lint! { /// Use instead: /// ```rust /// struct Thing; + /// /// impl Clone for Thing { /// fn clone(&self) -> Self { todo!() } /// fn clone_from(&mut self, other: &Self) { todo!() } @@ -47,7 +48,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.78.0"] pub ASSIGNING_CLONES, - perf, + pedantic, "assigning the result of cloning may be inefficient" } @@ -67,7 +68,8 @@ impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); impl<'tcx> LateLintPass<'tcx> for AssigningClones { fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) { // Do not fire the lint in macros - let expn_data = assign_expr.span().ctxt().outer_expn_data(); + let ctxt = assign_expr.span().ctxt(); + let expn_data = ctxt.outer_expn_data(); match expn_data.kind { ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return, ExpnKind::Root => {}, @@ -82,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones { }; if is_ok_to_suggest(cx, lhs, &call, &self.msrv) { - suggest(cx, assign_expr, lhs, &call); + suggest(cx, ctxt, assign_expr, lhs, &call); } } @@ -163,9 +165,7 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC // TODO: This check currently bails if the local variable has no initializer. // That is overly conservative - the lint should fire even if there was no initializer, // but the variable has been initialized before `lhs` was evaluated. - if let Some(Node::LetStmt(local)) = cx.tcx.hir().parent_id_iter(local).next().map(|p| cx.tcx.hir_node(p)) - && local.init.is_none() - { + if !local_is_initialized(cx, local) { return false; } } @@ -222,14 +222,20 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC implemented_fns.contains_key(&provided_fn.def_id) } -fn suggest<'tcx>(cx: &LateContext<'tcx>, assign_expr: &Expr<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>) { +fn suggest<'tcx>( + cx: &LateContext<'tcx>, + ctxt: SyntaxContext, + assign_expr: &Expr<'tcx>, + lhs: &Expr<'tcx>, + call: &CallCandidate<'tcx>, +) { span_lint_and_then(cx, ASSIGNING_CLONES, assign_expr.span, call.message(), |diag| { let mut applicability = Applicability::Unspecified; diag.span_suggestion( assign_expr.span, call.suggestion_msg(), - call.suggested_replacement(cx, lhs, &mut applicability), + call.suggested_replacement(cx, ctxt, lhs, &mut applicability), applicability, ); }); @@ -275,6 +281,7 @@ impl<'tcx> CallCandidate<'tcx> { fn suggested_replacement( &self, cx: &LateContext<'tcx>, + ctxt: SyntaxContext, lhs: &Expr<'tcx>, applicability: &mut Applicability, ) -> String { @@ -294,7 +301,7 @@ impl<'tcx> CallCandidate<'tcx> { // Determine whether we need to reference the argument to clone_from(). let clone_receiver_type = cx.typeck_results().expr_ty(receiver); let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(receiver); - let mut arg_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability); + let mut arg_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability); if clone_receiver_type != clone_receiver_adj_type { // The receiver may have been a value type, so we need to add an `&` to // be sure the argument to clone_from will be a reference. @@ -312,7 +319,7 @@ impl<'tcx> CallCandidate<'tcx> { Sugg::hir_with_applicability(cx, lhs, "_", applicability).mut_addr() }; // The RHS had to be exactly correct before the call, there is no auto-deref for function calls. - let rhs_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability); + let rhs_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability); format!("Clone::clone_from({self_sugg}, {rhs_sugg})") }, @@ -341,11 +348,11 @@ impl<'tcx> CallCandidate<'tcx> { match self.kind { CallKind::MethodCall { receiver } => { - let receiver_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability); + let receiver_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability); format!("{receiver_sugg}.clone_into({rhs_sugg})") }, CallKind::FunctionCall { self_arg, .. } => { - let self_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability); + let self_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability); format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})") }, } diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs index 736ee48641d8d..40a1c4e288422 100644 --- a/clippy_lints/src/attrs/duplicated_attributes.rs +++ b/clippy_lints/src/attrs/duplicated_attributes.rs @@ -36,9 +36,10 @@ fn check_duplicated_attr( } let Some(ident) = attr.ident() else { return }; let name = ident.name; - if name == sym::doc || name == sym::cfg_attr { + if name == sym::doc || name == sym::cfg_attr || name == sym::rustc_on_unimplemented { // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg // conditions are the same. + // `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected. return; } if let Some(direct_parent) = parent.last() diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 8f47bc7653b7c..39f4060779957 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -61,11 +61,21 @@ declare_clippy_lint! { /// /// This lint permits lint attributes for lints emitted on the items themself. /// For `use` items these lints are: + /// * ambiguous_glob_reexports + /// * dead_code /// * deprecated + /// * hidden_glob_reexports /// * unreachable_pub - /// * unused_imports + /// * unused + /// * unused_braces + /// * unused_import_braces + /// * clippy::disallowed_types /// * clippy::enum_glob_use /// * clippy::macro_use_imports + /// * clippy::module_name_repetitions + /// * clippy::redundant_pub_crate + /// * clippy::single_component_path_imports + /// * clippy::unsafe_removed_from_name /// * clippy::wildcard_imports /// /// For `extern crate` items these lints are: diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 7575f502a7c29..f0868231d01a3 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -2,6 +2,7 @@ use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use super::{Attribute, USELESS_ATTRIBUTE}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{first_line_of_span, snippet_opt}; +use rustc_ast::NestedMetaItem; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LintContext}; @@ -20,26 +21,40 @@ pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute]) for lint in lint_list { match item.kind { ItemKind::Use(..) => { - if is_word(lint, sym::unused_imports) - || is_word(lint, sym::deprecated) - || is_word(lint, sym!(unreachable_pub)) - || is_word(lint, sym!(unused)) - || is_word(lint, sym!(unused_import_braces)) - || extract_clippy_lint(lint).map_or(false, |s| { - matches!( - s.as_str(), - "wildcard_imports" - | "enum_glob_use" - | "redundant_pub_crate" - | "macro_use_imports" - | "unsafe_removed_from_name" - | "module_name_repetitions" - | "single_component_path_imports" - ) - }) + if let NestedMetaItem::MetaItem(meta_item) = lint + && meta_item.is_word() + && let Some(ident) = meta_item.ident() + && matches!( + ident.name.as_str(), + "ambiguous_glob_reexports" + | "dead_code" + | "deprecated" + | "hidden_glob_reexports" + | "unreachable_pub" + | "unused" + | "unused_braces" + | "unused_import_braces" + | "unused_imports" + ) { return; } + + if extract_clippy_lint(lint).is_some_and(|symbol| { + matches!( + symbol.as_str(), + "wildcard_imports" + | "enum_glob_use" + | "redundant_pub_crate" + | "macro_use_imports" + | "unsafe_removed_from_name" + | "module_name_repetitions" + | "single_component_path_imports" + | "disallowed_types" + ) + }) { + return; + } }, ItemKind::ExternCrate(..) => { if is_word(lint, sym::unused_imports) && skip_unused_imports { diff --git a/clippy_lints/src/cargo/lint_groups_priority.rs b/clippy_lints/src/cargo/lint_groups_priority.rs index a3291c9da1096..0d9eaac882f79 100644 --- a/clippy_lints/src/cargo/lint_groups_priority.rs +++ b/clippy_lints/src/cargo/lint_groups_priority.rs @@ -49,7 +49,7 @@ impl LintConfig { type LintTable = BTreeMap, Spanned>; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Default)] struct Lints { #[serde(default)] rust: LintTable, @@ -57,9 +57,18 @@ struct Lints { clippy: LintTable, } +#[derive(Deserialize, Debug, Default)] +struct Workspace { + #[serde(default)] + lints: Lints, +} + #[derive(Deserialize, Debug)] struct CargoToml { + #[serde(default)] lints: Lints, + #[serde(default)] + workspace: Workspace, } #[derive(Default, Debug)] @@ -164,5 +173,7 @@ pub fn check(cx: &LateContext<'_>) { check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file); check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file); + check_table(cx, cargo_toml.workspace.lints.rust, &rustc_groups, &file); + check_table(cx, cargo_toml.workspace.lints.clippy, &clippy_groups, &file); } } diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 2b6e17dc1030d..864489ee3fcd5 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -255,8 +255,10 @@ fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { /// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`], /// where the result depends on: +/// /// - the number of negative values in the entire expression, or /// - the number of negative values on the left hand side of the expression. +/// /// Ignores overflow. /// /// @@ -303,8 +305,10 @@ fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> { } /// Peels binary operators such as [`BinOpKind::Add`], where the result depends on: +/// /// - all the expressions being positive, or /// - all the expressions being negative. +/// /// Ignores overflow. /// /// Expressions using other operators are preserved, so we can try to evaluate them later. diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5ff7d8e513435..df2ef465700d4 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -140,6 +140,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::disallowed_names::DISALLOWED_NAMES_INFO, crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, + crate::doc::DOC_LAZY_CONTINUATION_INFO, crate::doc::DOC_LINK_WITH_QUOTES_INFO, crate::doc::DOC_MARKDOWN_INFO, crate::doc::EMPTY_DOCS_INFO, @@ -205,6 +206,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::functions::MUST_USE_CANDIDATE_INFO, crate::functions::MUST_USE_UNIT_INFO, crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO, + crate::functions::RENAMED_FUNCTION_PARAMS_INFO, crate::functions::RESULT_LARGE_ERR_INFO, crate::functions::RESULT_UNIT_ERR_INFO, crate::functions::TOO_MANY_ARGUMENTS_INFO, @@ -291,9 +293,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::loops::SAME_ITEM_PUSH_INFO, crate::loops::SINGLE_ELEMENT_LOOP_INFO, crate::loops::UNUSED_ENUMERATE_INDEX_INFO, + crate::loops::WHILE_FLOAT_INFO, crate::loops::WHILE_IMMUTABLE_CONDITION_INFO, crate::loops::WHILE_LET_LOOP_INFO, crate::loops::WHILE_LET_ON_ITERATOR_INFO, + crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO, crate::macro_use::MACRO_USE_IMPORTS_INFO, crate::main_recursion::MAIN_RECURSION_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs new file mode 100644 index 0000000000000..38bc58a550196 --- /dev/null +++ b/clippy_lints/src/doc/lazy_continuation.rs @@ -0,0 +1,95 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use itertools::Itertools; +use rustc_errors::{Applicability, SuggestionStyle}; +use rustc_lint::LateContext; +use rustc_span::{BytePos, Span}; +use std::ops::Range; + +use super::DOC_LAZY_CONTINUATION; + +fn map_container_to_text(c: &super::Container) -> &'static str { + match c { + super::Container::Blockquote => "> ", + // numbered list can have up to nine digits, plus the dot, plus four spaces on either side + super::Container::List(indent) => &" "[0..*indent], + } +} + +// TODO: Adjust the parameters as necessary +pub(super) fn check( + cx: &LateContext<'_>, + doc: &str, + range: Range, + mut span: Span, + containers: &[super::Container], +) { + if doc[range.clone()].contains('\t') { + // We don't do tab stops correctly. + return; + } + + let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count(); + let blockquote_level = containers + .iter() + .filter(|c| matches!(c, super::Container::Blockquote)) + .count(); + let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count(); + let list_indentation = containers + .iter() + .map(|c| { + if let super::Container::List(indent) = c { + *indent + } else { + 0 + } + }) + .sum(); + if ccount < blockquote_level || lcount < list_indentation { + let msg = if ccount < blockquote_level { + "doc quote missing `>` marker" + } else { + "doc list item missing indentation" + }; + span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { + if ccount == 0 && blockquote_level == 0 { + // simpler suggestion style for indentation + let indent = list_indentation - lcount; + diag.span_suggestion_with_style( + span.shrink_to_hi(), + "indent this line", + std::iter::repeat(" ").take(indent).join(""), + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + diag.help("if this is supposed to be its own paragraph, add a blank line"); + return; + } + let mut doc_start_range = &doc[range]; + let mut suggested = String::new(); + for c in containers { + let text = map_container_to_text(c); + if doc_start_range.starts_with(text) { + doc_start_range = &doc_start_range[text.len()..]; + span = span + .with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger"))); + } else if matches!(c, super::Container::Blockquote) + && let Some(i) = doc_start_range.find('>') + { + doc_start_range = &doc_start_range[i + 1..]; + span = + span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1)); + } else { + suggested.push_str(text); + } + } + diag.span_suggestion_with_style( + span, + "add markers to start of line", + suggested, + Applicability::MachineApplicable, + SuggestionStyle::ShowAlways, + ); + diag.help("if this not intended to be a quote at all, escape it with `\\>`"); + }); + } +} diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs index 36ba19698c72f..010fab803d99c 100644 --- a/clippy_lints/src/doc/missing_headers.rs +++ b/clippy_lints/src/doc/missing_headers.rs @@ -1,3 +1,4 @@ +use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC}; use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{is_doc_hidden, return_ty}; @@ -6,15 +7,13 @@ use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{sym, Span}; -use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC}; - pub fn check( cx: &LateContext<'_>, owner_id: OwnerId, sig: FnSig<'_>, headers: DocHeaders, body_id: Option, - panic_span: Option, + panic_info: Option<(Span, bool)>, check_private_items: bool, ) { if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) { @@ -48,13 +47,13 @@ pub fn check( ), _ => (), } - if !headers.panics && panic_span.is_some() { + if !headers.panics && panic_info.map_or(false, |el| !el.1) { span_lint_and_note( cx, MISSING_PANICS_DOC, span, "docs for function which may panic missing `# Panics` section", - panic_span, + panic_info.map(|el| el.0), "first possible panic found here", ); } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 7fdb582e64033..9f08973948a32 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -1,13 +1,14 @@ +mod lazy_continuation; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::Visitable; -use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args}; +use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use pulldown_cmark::Event::{ Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, }; -use pulldown_cmark::Tag::{BlockQuote, CodeBlock, Heading, Item, Link, Paragraph}; +use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph}; use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options}; use rustc_ast::ast::Attribute; use rustc_data_structures::fx::FxHashSet; @@ -362,6 +363,63 @@ declare_clippy_lint! { "docstrings exist but documentation is empty" } +declare_clippy_lint! { + /// ### What it does + /// + /// In CommonMark Markdown, the language used to write doc comments, a + /// paragraph nested within a list or block quote does not need any line + /// after the first one to be indented or marked. The specification calls + /// this a "lazy paragraph continuation." + /// + /// ### Why is this bad? + /// + /// This is easy to write but hard to read. Lazy continuations makes + /// unintended markers hard to see, and make it harder to deduce the + /// document's intended structure. + /// + /// ### Example + /// + /// This table is probably intended to have two rows, + /// but it does not. It has zero rows, and is followed by + /// a block quote. + /// ```no_run + /// /// Range | Description + /// /// ----- | ----------- + /// /// >= 1 | fully opaque + /// /// < 1 | partially see-through + /// fn set_opacity(opacity: f32) {} + /// ``` + /// + /// Fix it by escaping the marker: + /// ```no_run + /// /// Range | Description + /// /// ----- | ----------- + /// /// \>= 1 | fully opaque + /// /// < 1 | partially see-through + /// fn set_opacity(opacity: f32) {} + /// ``` + /// + /// This example is actually intended to be a list: + /// ```no_run + /// /// * Do nothing. + /// /// * Then do something. Whatever it is needs done, + /// /// it should be done right now. + /// # fn do_stuff() {} + /// ``` + /// + /// Fix it by indenting the list contents: + /// ```no_run + /// /// * Do nothing. + /// /// * Then do something. Whatever it is needs done, + /// /// it should be done right now. + /// # fn do_stuff() {} + /// ``` + #[clippy::version = "1.80.0"] + pub DOC_LAZY_CONTINUATION, + style, + "require every line of a paragraph to be indented and marked" +} + #[derive(Clone)] pub struct Documentation { valid_idents: FxHashSet, @@ -388,6 +446,7 @@ impl_lint_pass!(Documentation => [ UNNECESSARY_SAFETY_DOC, SUSPICIOUS_DOC_COMMENTS, EMPTY_DOCS, + DOC_LAZY_CONTINUATION, ]); impl<'tcx> LateLintPass<'tcx> for Documentation { @@ -402,14 +461,14 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) { let body = cx.tcx.hir().body(body_id); - let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); + let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); missing_headers::check( cx, item.owner_id, sig, headers, Some(body_id), - panic_span, + panic_info, self.check_private_items, ); } @@ -551,6 +610,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ cx, valid_idents, parser.into_offset_iter(), + &doc, Fragments { fragments: &fragments, doc: &doc, @@ -560,6 +620,11 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"]; +enum Container { + Blockquote, + List(usize), +} + /// Checks parsed documentation. /// This walks the "events" (think sections of markdown) produced by `pulldown_cmark`, /// so lints here will generally access that information. @@ -569,6 +634,7 @@ fn check_doc<'a, Events: Iterator, Range, valid_idents: &FxHashSet, events: Events, + doc: &str, fragments: Fragments<'_>, ) -> DocHeaders { // true if a safety header was found @@ -576,6 +642,7 @@ fn check_doc<'a, Events: Iterator, Range, Range { if tag.starts_with(", Range blockquote_level += 1, - End(BlockQuote) => blockquote_level -= 1, + Start(BlockQuote) => { + blockquote_level += 1; + containers.push(Container::Blockquote); + }, + End(BlockQuote) => { + blockquote_level -= 1; + containers.pop(); + }, Start(CodeBlock(ref kind)) => { in_code = true; if let CodeBlockKind::Fenced(lang) = kind { @@ -633,6 +710,13 @@ fn check_doc<'a, Events: Iterator, Range, Range, Range in_footnote_definition = true, + End(FootnoteDefinition(..)) => in_footnote_definition = false, Start(_tag) | End(_tag) => (), // We don't care about other tags - SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (), + SoftBreak | HardBreak => { + if !containers.is_empty() + && let Some((next_event, next_range)) = events.peek() + && let Some(next_span) = fragments.span(cx, next_range.clone()) + && let Some(span) = fragments.span(cx, range.clone()) + && !in_footnote_definition + && !matches!(next_event, End(_)) + { + lazy_continuation::check( + cx, + doc, + range.end..next_range.start, + Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()), + &containers[..], + ); + } + }, + TaskListMarker(_) | Code(_) | Rule => (), FootnoteReference(text) | Text(text) => { paragraph_range.end = range.end; ticks_unbalanced |= text.contains('`') && !in_code; @@ -701,6 +807,7 @@ fn check_doc<'a, Events: Iterator, Range { cx: &'a LateContext<'tcx>, + is_const: bool, panic_span: Option, typeck_results: &'tcx ty::TypeckResults<'tcx>, } @@ -710,14 +817,15 @@ impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> { cx: &'a LateContext<'tcx>, typeck_results: &'tcx ty::TypeckResults<'tcx>, body: impl Visitable<'tcx>, - ) -> Option { + ) -> Option<(Span, bool)> { let mut vis = Self { cx, + is_const: false, panic_span: None, typeck_results, }; body.visit(&mut vis); - vis.panic_span + vis.panic_span.map(|el| (el, vis.is_const)) } } @@ -736,6 +844,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { "assert" | "assert_eq" | "assert_ne" ) { + self.is_const = in_constant(self.cx, expr.hir_id); self.panic_span = Some(macro_call.span); } } diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 6715de52649d4..8d6e27700d8d4 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -104,7 +104,9 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { too_large_for_stack: self.too_large_for_stack, }; - ExprUseVisitor::for_clippy(cx, fn_def_id, &mut v).consume_body(body).into_ok(); + ExprUseVisitor::for_clippy(cx, fn_def_id, &mut v) + .consume_body(body) + .into_ok(); for node in v.set { span_lint_hir( diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 724e1843359b2..3c4a043a732b7 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_args, format_args_inputs_span}; +use clippy_utils::macros::{format_args_inputs_span, FormatArgsStorage}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_expn_of, path_def_id}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, ExpnId}; declare_clippy_lint! { @@ -38,7 +38,17 @@ declare_clippy_lint! { "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work" } -declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); +pub struct ExplicitWrite { + format_args: FormatArgsStorage, +} + +impl ExplicitWrite { + pub fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } +} + +impl_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]); impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -57,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { Some(sym::io_stderr) => ("stderr", "e"), _ => return, }; - let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root()) else { + let Some(format_args) = self.format_args.get(cx, write_arg, ExpnId::root()) else { return; }; @@ -83,7 +93,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { }; let mut applicability = Applicability::MachineApplicable; let inputs_snippet = - snippet_with_applicability(cx, format_args_inputs_span(&format_args), "..", &mut applicability); + snippet_with_applicability(cx, format_args_inputs_span(format_args), "..", &mut applicability); span_lint_and_sugg( cx, EXPLICIT_WRITE, diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 8a0cd155d211c..0b248f784b762 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node}; +use clippy_utils::macros::{find_format_arg_expr, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::Sugg; use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait}; @@ -7,7 +7,7 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -39,13 +39,24 @@ declare_clippy_lint! { "useless use of `format!`" } -declare_lint_pass!(UselessFormat => [USELESS_FORMAT]); +#[allow(clippy::module_name_repetitions)] +pub struct UselessFormat { + format_args: FormatArgsStorage, +} + +impl UselessFormat { + pub fn new(format_args: FormatArgsStorage) -> Self { + Self { format_args } + } +} + +impl_lint_pass!(UselessFormat => [USELESS_FORMAT]); impl<'tcx> LateLintPass<'tcx> for UselessFormat { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) && cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, expr, macro_call.expn) + && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { let mut applicability = Applicability::MachineApplicable; let call_site = macro_call.span; diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 003a9995c15f3..86115807aa4d1 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -3,8 +3,8 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::is_diag_trait_item; use clippy_utils::macros::{ - find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, - is_format_macro, is_panic, matching_root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall, + find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, + is_panic, matching_root_macro_call, root_macro_call_first_node, FormatArgsStorage, FormatParamUsage, MacroCall, }; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{implements_trait, is_type_lang_item}; @@ -167,15 +167,18 @@ impl_lint_pass!(FormatArgs => [ UNUSED_FORMAT_SPECS, ]); +#[allow(clippy::struct_field_names)] pub struct FormatArgs { + format_args: FormatArgsStorage, msrv: Msrv, ignore_mixed: bool, } impl FormatArgs { #[must_use] - pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self { + pub fn new(format_args: FormatArgsStorage, msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self { Self { + format_args, msrv, ignore_mixed: allow_mixed_uninlined_format_args, } @@ -186,13 +189,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) && is_format_macro(cx, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, expr, macro_call.expn) + && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { let linter = FormatArgsExpr { cx, expr, macro_call: ¯o_call, - format_args: &format_args, + format_args, ignore_mixed: self.ignore_mixed, }; diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 0a52347940abb..09be7237b5ca3 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; -use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node}; +use clippy_utils::macros::{find_format_arg_expr, is_format_macro, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; use rustc_ast::{FormatArgsPiece, FormatTrait}; use rustc_errors::Applicability; @@ -99,13 +99,15 @@ struct FormatTraitNames { #[derive(Default)] pub struct FormatImpl { + format_args: FormatArgsStorage, // Whether we are inside Display or Debug trait impl - None for neither format_trait_impl: Option, } impl FormatImpl { - pub fn new() -> Self { + pub fn new(format_args: FormatArgsStorage) -> Self { Self { + format_args, format_trait_impl: None, } } @@ -129,6 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl { if let Some(format_trait_impl) = self.format_trait_impl { let linter = FormatImplExpr { cx, + format_args: &self.format_args, expr, format_trait_impl, }; @@ -141,6 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl { struct FormatImplExpr<'a, 'tcx> { cx: &'a LateContext<'tcx>, + format_args: &'a FormatArgsStorage, expr: &'tcx Expr<'tcx>, format_trait_impl: FormatTraitNames, } @@ -175,7 +179,7 @@ impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> { if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr) && let macro_def_id = outer_macro.def_id && is_format_macro(self.cx, macro_def_id) - && let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn) + && let Some(format_args) = self.format_args.get(self.cx, self.expr, outer_macro.expn) { for piece in &format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index 633ed96d6a6d5..82ce501bac59b 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_integer_literal; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::{in_constant, is_integer_literal}; use rustc_errors::Applicability; use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -47,6 +47,9 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) { if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind && let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind + // do not lint in constant context, because the suggestion won't work. + // NB: keep this check until a new `const_trait_impl` is available and stablized. + && !in_constant(cx, exp.hir_id) // check if the first part of the path is some integer primitive && let TyKind::Path(ty_qpath) = &ty.kind diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 9cc51fa8cd5dd..dfcaac9abefc7 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -2,15 +2,17 @@ mod impl_trait_in_params; mod misnamed_getters; mod must_use; mod not_unsafe_ptr_arg_deref; +mod renamed_function_params; mod result; mod too_many_arguments; mod too_many_lines; +use clippy_utils::def_path_def_ids; use rustc_hir as hir; use rustc_hir::intravisit; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::def_id::LocalDefId; +use rustc_span::def_id::{DefIdSet, LocalDefId}; use rustc_span::Span; declare_clippy_lint! { @@ -359,13 +361,51 @@ declare_clippy_lint! { "`impl Trait` is used in the function's parameters" } -#[derive(Copy, Clone)] -#[allow(clippy::struct_field_names)] +declare_clippy_lint! { + /// ### What it does + /// Lints when the name of function parameters from trait impl is + /// different than its default implementation. + /// + /// ### Why is this bad? + /// Using the default name for parameters of a trait method is often + /// more desirable for consistency's sake. + /// + /// ### Example + /// ```rust + /// struct A(u32); + /// + /// impl PartialEq for A { + /// fn eq(&self, b: &Self) -> bool { + /// self.0 == b.0 + /// } + /// } + /// ``` + /// Use instead: + /// ```rust + /// struct A(u32); + /// + /// impl PartialEq for A { + /// fn eq(&self, other: &Self) -> bool { + /// self.0 == other.0 + /// } + /// } + /// ``` + #[clippy::version = "1.74.0"] + pub RENAMED_FUNCTION_PARAMS, + restriction, + "renamed function parameters in trait implementation" +} + +#[derive(Clone)] pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64, avoid_breaking_exported_api: bool, + allow_renamed_params_for: Vec, + /// A set of resolved `def_id` of traits that are configured to allow + /// function params renaming. + trait_ids: DefIdSet, } impl Functions { @@ -374,12 +414,15 @@ impl Functions { too_many_lines_threshold: u64, large_error_threshold: u64, avoid_breaking_exported_api: bool, + allow_renamed_params_for: Vec, ) -> Self { Self { too_many_arguments_threshold, too_many_lines_threshold, large_error_threshold, avoid_breaking_exported_api, + allow_renamed_params_for, + trait_ids: DefIdSet::default(), } } } @@ -395,6 +438,7 @@ impl_lint_pass!(Functions => [ RESULT_LARGE_ERR, MISNAMED_GETTERS, IMPL_TRAIT_IN_PARAMS, + RENAMED_FUNCTION_PARAMS, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -424,6 +468,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { must_use::check_impl_item(cx, item); result::check_impl_item(cx, item, self.large_error_threshold); impl_trait_in_params::check_impl_item(cx, item); + renamed_function_params::check_impl_item(cx, item, &self.trait_ids); } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { @@ -433,4 +478,12 @@ impl<'tcx> LateLintPass<'tcx> for Functions { result::check_trait_item(cx, item, self.large_error_threshold); impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api); } + + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + for path in &self.allow_renamed_params_for { + let path_segments: Vec<&str> = path.split("::").collect(); + let ids = def_path_def_ids(cx, &path_segments); + self.trait_ids.extend(ids); + } + } } diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs new file mode 100644 index 0000000000000..c7de0385c021b --- /dev/null +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -0,0 +1,110 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::{Applicability, MultiSpan}; +use rustc_hir::def_id::{DefId, DefIdSet}; +use rustc_hir::hir_id::OwnerId; +use rustc_hir::{Impl, ImplItem, ImplItemKind, ImplItemRef, ItemKind, Node, TraitRef}; +use rustc_lint::LateContext; +use rustc_span::symbol::{kw, Ident, Symbol}; +use rustc_span::Span; + +use super::RENAMED_FUNCTION_PARAMS; + +pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>, ignored_traits: &DefIdSet) { + if !item.span.from_expansion() + && let ImplItemKind::Fn(_, body_id) = item.kind + && let parent_node = cx.tcx.parent_hir_node(item.hir_id()) + && let Node::Item(parent_item) = parent_node + && let ItemKind::Impl(Impl { + items, + of_trait: Some(trait_ref), + .. + }) = &parent_item.kind + && let Some(did) = trait_item_def_id_of_impl(items, item.owner_id) + && !is_from_ignored_trait(trait_ref, ignored_traits) + { + let mut param_idents_iter = cx.tcx.hir().body_param_names(body_id); + let mut default_param_idents_iter = cx.tcx.fn_arg_names(did).iter().copied(); + + let renames = RenamedFnArgs::new(&mut default_param_idents_iter, &mut param_idents_iter); + if !renames.0.is_empty() { + let multi_span = renames.multi_span(); + let plural = if renames.0.len() == 1 { "" } else { "s" }; + span_lint_and_then( + cx, + RENAMED_FUNCTION_PARAMS, + multi_span, + format!("renamed function parameter{plural} of trait impl"), + |diag| { + diag.multipart_suggestion( + format!("consider using the default name{plural}"), + renames.0, + Applicability::Unspecified, + ); + }, + ); + } + } +} + +struct RenamedFnArgs(Vec<(Span, String)>); + +impl RenamedFnArgs { + /// Comparing between an iterator of default names and one with current names, + /// then collect the ones that got renamed. + fn new(default_names: &mut I, current_names: &mut T) -> Self + where + I: Iterator, + T: Iterator, + { + let mut renamed: Vec<(Span, String)> = vec![]; + + debug_assert!(default_names.size_hint() == current_names.size_hint()); + while let (Some(def_name), Some(cur_name)) = (default_names.next(), current_names.next()) { + let current_name = cur_name.name; + let default_name = def_name.name; + if is_unused_or_empty_symbol(current_name) || is_unused_or_empty_symbol(default_name) { + continue; + } + if current_name != default_name { + renamed.push((cur_name.span, default_name.to_string())); + } + } + + Self(renamed) + } + + fn multi_span(&self) -> MultiSpan { + self.0 + .iter() + .map(|(span, _)| span) + .copied() + .collect::>() + .into() + } +} + +fn is_unused_or_empty_symbol(symbol: Symbol) -> bool { + // FIXME: `body_param_names` currently returning empty symbols for `wild` as well, + // so we need to check if the symbol is empty first. + // Therefore the check of whether it's equal to [`kw::Underscore`] has no use for now, + // but it would be nice to keep it here just to be future-proof. + symbol.is_empty() || symbol == kw::Underscore || symbol.as_str().starts_with('_') +} + +/// Get the [`trait_item_def_id`](ImplItemRef::trait_item_def_id) of a relevant impl item. +fn trait_item_def_id_of_impl(items: &[ImplItemRef], target: OwnerId) -> Option { + items.iter().find_map(|item| { + if item.id.owner_id == target { + item.trait_item_def_id + } else { + None + } + }) +} + +fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool { + let Some(trait_did) = of_trait.trait_def_id() else { + return false; + }; + ignored_traits.contains(&trait_did) +} diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index 2c2daac023492..192fb611c2d4b 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -4,8 +4,8 @@ use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind}; use rustc_middle::ty::print::PrintTraitRefExt; +use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind}; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index a8bfbbdd9ecab..3328d642bd855 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -61,11 +61,6 @@ extern crate clippy_utils; #[macro_use] extern crate declare_clippy_lint; -use std::collections::BTreeMap; - -use rustc_data_structures::fx::FxHashSet; -use rustc_lint::{Lint, LintId}; - #[cfg(feature = "internal")] pub mod deprecated_lints; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] @@ -199,6 +194,7 @@ mod lifetimes; mod lines_filter_map_ok; mod literal_representation; mod loops; +mod macro_metavars_in_unsafe; mod macro_use; mod main_recursion; mod manual_assert; @@ -385,6 +381,10 @@ mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` use clippy_config::{get_configuration_metadata, Conf}; +use clippy_utils::macros::FormatArgsStorage; +use rustc_data_structures::fx::FxHashSet; +use rustc_lint::{Lint, LintId}; +use std::collections::BTreeMap; /// Register all pre expansion lints /// @@ -533,6 +533,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { allow_expect_in_tests, allow_mixed_uninlined_format_args, allow_one_hash_in_raw_strings, + allow_panic_in_tests, allow_print_in_tests, allow_private_module_inception, allow_unwrap_in_tests, @@ -597,9 +598,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { ref allowed_duplicate_crates, allow_comparison_to_zero, ref allowed_prefixes, + ref allow_renamed_params_for, blacklisted_names: _, cyclomatic_complexity_threshold: _, + warn_unsafe_macro_metavars_in_private_macros, } = *conf; let msrv = || msrv.clone(); @@ -616,6 +619,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { } } + let format_args_storage = FormatArgsStorage::default(); + let format_args = format_args_storage.clone(); + store.register_early_pass(move || { + Box::new(utils::format_args_collector::FormatArgsCollector::new( + format_args.clone(), + )) + }); + // all the internal lints #[cfg(feature = "internal")] { @@ -656,7 +667,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { .collect(), )) }); - store.register_early_pass(|| Box::::default()); store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|_| Box::new(utils::author::Author)); store.register_late_pass(move |_| { @@ -698,6 +708,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv()))); + let format_args = format_args_storage.clone(); store.register_late_pass(move |_| { Box::new(methods::Methods::new( avoid_breaking_exported_api, @@ -705,6 +716,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { allow_expect_in_tests, allow_unwrap_in_tests, allowed_dotfiles.clone(), + format_args.clone(), )) }); store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv()))); @@ -759,7 +771,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { allow_in_test: allow_useless_vec_in_tests, }) }); - store.register_late_pass(|_| Box::new(panic_unimplemented::PanicUnimplemented)); + store.register_late_pass(move |_| Box::new(panic_unimplemented::PanicUnimplemented { allow_panic_in_tests })); store.register_late_pass(|_| Box::new(strings::StringLitAsBytes)); store.register_late_pass(|_| Box::new(derive::Derive)); store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(msrv()))); @@ -769,7 +781,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone()))); store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); - store.register_late_pass(|_| Box::new(format::UselessFormat)); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); store.register_late_pass(|_| Box::new(swap::Swap)); store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional)); store.register_late_pass(|_| Box::::default()); @@ -780,6 +793,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { too_many_lines_threshold, large_error_threshold, avoid_breaking_exported_api, + allow_renamed_params_for.clone(), )) }); store.register_late_pass(move |_| Box::new(doc::Documentation::new(doc_valid_idents, check_private_items))); @@ -793,7 +807,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold))); - store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite)); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))); store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)); store.register_late_pass(move |tcx| { Box::new(pass_by_ref_or_value::PassByRefOrValue::new( @@ -835,7 +850,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone()))); store.register_early_pass(|| Box::new(reference::DerefAddrOf)); store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new())); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); @@ -961,8 +977,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { accept_comment_above_attributes, )) }); - store - .register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args))); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| { + Box::new(format_args::FormatArgs::new( + format_args.clone(), + msrv(), + allow_mixed_uninlined_format_args, + )) + }); store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray)); store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); @@ -973,7 +995,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests))); - store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests))); + let format_args = format_args_storage.clone(); + store.register_late_pass(move |_| Box::new(write::Write::new(format_args.clone(), allow_print_in_tests))); store.register_late_pass(move |_| { Box::new(cargo::Cargo { ignore_publish: cargo_ignore_publish, @@ -1136,6 +1159,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault)); store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); + store.register_late_pass(move |_| { + Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe { + warn_unsafe_macro_metavars_in_private_macros, + ..Default::default() + }) + }); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index b5e39b33c6a15..3dcb050d77e68 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -17,6 +17,7 @@ mod same_item_push; mod single_element_loop; mod unused_enumerate_index; mod utils; +mod while_float; mod while_immutable_condition; mod while_let_loop; mod while_let_on_iterator; @@ -416,6 +417,39 @@ declare_clippy_lint! { "variables used within while expression are not mutated in the body" } +declare_clippy_lint! { + /// ### What it does + /// Checks for while loops comparing floating point values. + /// + /// ### Why is this bad? + /// If you increment floating point values, errors can compound, + /// so, use integers instead if possible. + /// + /// ### Known problems + /// The lint will catch all while loops comparing floating point + /// values without regarding the increment. + /// + /// ### Example + /// ```no_run + /// let mut x = 0.0; + /// while x < 42.0 { + /// x += 1.0; + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// let mut x = 0; + /// while x < 42 { + /// x += 1; + /// } + /// ``` + #[clippy::version = "1.80.0"] + pub WHILE_FLOAT, + nursery, + "while loops comaparing floating point values" +} + declare_clippy_lint! { /// ### What it does /// Checks whether a for loop is being used to push a constant @@ -706,6 +740,7 @@ impl_lint_pass!(Loops => [ NEVER_LOOP, MUT_RANGE_BOUND, WHILE_IMMUTABLE_CONDITION, + WHILE_FLOAT, SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, @@ -762,6 +797,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops { if let Some(higher::While { condition, body, span }) = higher::While::hir(expr) { while_immutable_condition::check(cx, condition, body); + while_float::check(cx, condition); missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index 6b9ecf5f14130..6c6a9a1a2e000 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -60,12 +60,9 @@ fn check_for_mutation( span_low: None, span_high: None, }; - ExprUseVisitor::for_clippy( - cx, - body.hir_id.owner.def_id, - &mut delegate, - ) - .walk_expr(body).into_ok(); + ExprUseVisitor::for_clippy(cx, body.hir_id.owner.def_id, &mut delegate) + .walk_expr(body) + .into_ok(); delegate.mutation_span() } diff --git a/clippy_lints/src/loops/while_float.rs b/clippy_lints/src/loops/while_float.rs new file mode 100644 index 0000000000000..cf62ce29f0c7a --- /dev/null +++ b/clippy_lints/src/loops/while_float.rs @@ -0,0 +1,20 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_hir::ExprKind; + +pub(super) fn check(cx: &rustc_lint::LateContext<'_>, condition: &rustc_hir::Expr<'_>) { + if let ExprKind::Binary(_op, left, right) = condition.kind + && is_float_type(cx, left) + && is_float_type(cx, right) + { + span_lint( + cx, + super::WHILE_FLOAT, + condition.span, + "while condition comparing floats", + ); + } +} + +fn is_float_type(cx: &rustc_lint::LateContext<'_>, expr: &rustc_hir::Expr<'_>) -> bool { + cx.typeck_results().expr_ty(expr).is_floating_point() +} diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs new file mode 100644 index 0000000000000..aea3d26e18788 --- /dev/null +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -0,0 +1,256 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; + +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::is_lint_allowed; +use itertools::Itertools; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor}; +use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::{sym, Span, SyntaxContext}; + +declare_clippy_lint! { + /// ### What it does + /// Looks for macros that expand metavariables in an unsafe block. + /// + /// ### Why is this bad? + /// This hides an unsafe block and allows the user of the macro to write unsafe code without an explicit + /// unsafe block at callsite, making it possible to perform unsafe operations in seemingly safe code. + /// + /// The macro should be restructured so that these metavariables are referenced outside of unsafe blocks + /// and that the usual unsafety checks apply to the macro argument. + /// + /// This is usually done by binding it to a variable outside of the unsafe block + /// and then using that variable inside of the block as shown in the example, or by referencing it a second time + /// in a safe context, e.g. `if false { $expr }`. + /// + /// ### Known limitations + /// Due to how macros are represented in the compiler at the time Clippy runs its lints, + /// it's not possible to look for metavariables in macro definitions directly. + /// + /// Instead, this lint looks at expansions of macros. + /// This leads to false negatives for macros that are never actually invoked. + /// + /// By default, this lint is rather conservative and will only emit warnings on publicly-exported + /// macros from the same crate, because oftentimes private internal macros are one-off macros where + /// this lint would just be noise (e.g. macros that generate `impl` blocks). + /// The default behavior should help with preventing a high number of such false positives, + /// however it can be configured to also emit warnings in private macros if desired. + /// + /// ### Example + /// ```no_run + /// /// Gets the first element of a slice + /// macro_rules! first { + /// ($slice:expr) => { + /// unsafe { + /// let slice = $slice; // ⚠️ expansion inside of `unsafe {}` + /// + /// assert!(!slice.is_empty()); + /// // SAFETY: slice is checked to have at least one element + /// slice.first().unwrap_unchecked() + /// } + /// } + /// } + /// + /// assert_eq!(*first!(&[1i32]), 1); + /// + /// // This will compile as a consequence (note the lack of `unsafe {}`) + /// assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1); + /// ``` + /// Use instead: + /// ```compile_fail + /// macro_rules! first { + /// ($slice:expr) => {{ + /// let slice = $slice; // âś… outside of `unsafe {}` + /// unsafe { + /// assert!(!slice.is_empty()); + /// // SAFETY: slice is checked to have at least one element + /// slice.first().unwrap_unchecked() + /// } + /// }} + /// } + /// + /// assert_eq!(*first!(&[1]), 1); + /// + /// // This won't compile: + /// assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1); + /// ``` + #[clippy::version = "1.80.0"] + pub MACRO_METAVARS_IN_UNSAFE, + suspicious, + "expanding macro metavariables in an unsafe block" +} +impl_lint_pass!(ExprMetavarsInUnsafe => [MACRO_METAVARS_IN_UNSAFE]); + +#[derive(Clone, Debug)] +pub enum MetavarState { + ReferencedInUnsafe { unsafe_blocks: Vec }, + ReferencedInSafe, +} + +#[derive(Default)] +pub struct ExprMetavarsInUnsafe { + pub warn_unsafe_macro_metavars_in_private_macros: bool, + /// A metavariable can be expanded more than once, potentially across multiple bodies, so it + /// requires some state kept across HIR nodes to make it possible to delay a warning + /// and later undo: + /// + /// ```ignore + /// macro_rules! x { + /// ($v:expr) => { + /// unsafe { $v; } // unsafe context, it might be possible to emit a warning here, so add it to the map + /// + /// $v; // `$v` expanded another time but in a safe context, set to ReferencedInSafe to suppress + /// } + /// } + /// ``` + pub metavar_expns: BTreeMap, +} + +struct BodyVisitor<'a, 'tcx> { + /// Stack of unsafe blocks -- the top item always represents the last seen unsafe block from + /// within a relevant macro. + macro_unsafe_blocks: Vec, + /// When this is >0, it means that the node currently being visited is "within" a + /// macro definition. This is not necessary for correctness, it merely helps reduce the number + /// of spans we need to insert into the map, since only spans from macros are relevant. + expn_depth: u32, + cx: &'a LateContext<'tcx>, + lint: &'a mut ExprMetavarsInUnsafe, +} + +fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { + (cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export)) + && !cx.tcx.is_doc_hidden(def_id) +} + +impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> { + fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { + let from_expn = s.span.from_expansion(); + if from_expn { + self.expn_depth += 1; + } + walk_stmt(self, s); + if from_expn { + self.expn_depth -= 1; + } + } + + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + let ctxt = e.span.ctxt(); + + if let ExprKind::Block(block, _) = e.kind + && let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules + && !ctxt.is_root() + && let Some(macro_def_id) = ctxt.outer_expn_data().macro_def_id + && let Some(macro_def_id) = macro_def_id.as_local() + && (self.lint.warn_unsafe_macro_metavars_in_private_macros || is_public_macro(self.cx, macro_def_id)) + { + self.macro_unsafe_blocks.push(block.hir_id); + walk_block(self, block); + self.macro_unsafe_blocks.pop(); + } else if ctxt.is_root() && self.expn_depth > 0 { + let unsafe_block = self.macro_unsafe_blocks.last().copied(); + + match (self.lint.metavar_expns.entry(e.span), unsafe_block) { + (Entry::Vacant(e), None) => { + e.insert(MetavarState::ReferencedInSafe); + }, + (Entry::Vacant(e), Some(unsafe_block)) => { + e.insert(MetavarState::ReferencedInUnsafe { + unsafe_blocks: vec![unsafe_block], + }); + }, + (Entry::Occupied(mut e), None) => { + if let MetavarState::ReferencedInUnsafe { .. } = *e.get() { + e.insert(MetavarState::ReferencedInSafe); + } + }, + (Entry::Occupied(mut e), Some(unsafe_block)) => { + if let MetavarState::ReferencedInUnsafe { unsafe_blocks } = e.get_mut() + && !unsafe_blocks.contains(&unsafe_block) + { + unsafe_blocks.push(unsafe_block); + } + }, + } + + // NB: No need to visit descendant nodes. They're guaranteed to represent the same + // metavariable + } else { + walk_expr(self, e); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe { + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx rustc_hir::Body<'tcx>) { + if is_lint_allowed(cx, MACRO_METAVARS_IN_UNSAFE, body.value.hir_id) { + return; + } + + // This BodyVisitor is separate and not part of the lint pass because there is no + // `check_stmt_post` on `(Late)LintPass`, which we'd need to detect when we're leaving a macro span + + let mut vis = BodyVisitor { + #[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning + expn_depth: if body.value.span.from_expansion() { 1 } else { 0 }, + macro_unsafe_blocks: Vec::new(), + lint: self, + cx + }; + vis.visit_body(body); + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + // Aggregate all unsafe blocks from all spans: + // ``` + // macro_rules! x { + // ($w:expr, $x:expr, $y:expr) => { $w; unsafe { $w; $x; }; unsafe { $x; $y; }; } + // } + // $w: [] (unsafe#0 is never added because it was referenced in a safe context) + // $x: [unsafe#0, unsafe#1] + // $y: [unsafe#1] + // ``` + // We want to lint unsafe blocks #0 and #1 + let bad_unsafe_blocks = self + .metavar_expns + .iter() + .filter_map(|(_, state)| match state { + MetavarState::ReferencedInUnsafe { unsafe_blocks } => Some(unsafe_blocks.as_slice()), + MetavarState::ReferencedInSafe => None, + }) + .flatten() + .copied() + .map(|id| { + // Remove the syntax context to hide "in this macro invocation" in the diagnostic. + // The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything + // related to the callsite. + let span = cx.tcx.hir().span(id); + + (id, Span::new(span.lo(), span.hi(), SyntaxContext::root(), None)) + }) + .dedup_by(|&(_, a), &(_, b)| a == b); + + for (id, span) in bad_unsafe_blocks { + span_lint_hir_and_then( + cx, + MACRO_METAVARS_IN_UNSAFE, + id, + span, + "this macro expands metavariables in an unsafe block", + |diag| { + diag.note("this allows the user of the macro to write unsafe code outside of an unsafe block"); + diag.help( + "consider expanding any metavariables outside of this block, e.g. by storing them in a variable", + ); + diag.help( + "... or also expand referenced metavariables in a safe context to require an unsafe block at callsite", + ); + }, + ); + } + } +} diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 1eadc200bedc8..e2ab441551824 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -611,15 +611,22 @@ impl<'tcx> BinaryOp<'tcx> { /// The clamp meta pattern is a pattern shared between many (but not all) patterns. /// In summary, this pattern consists of two if statements that meet many criteria, +/// /// - binary operators that are one of [`>`, `<`, `>=`, `<=`]. +/// /// - Both binary statements must have a shared argument +/// /// - Which can appear on the left or right side of either statement +/// /// - The binary operators must define a finite range for the shared argument. To put this in /// the terms of Rust `std` library, the following ranges are acceptable +/// /// - `Range` /// - `RangeInclusive` +/// /// And all other range types are not accepted. For the purposes of `clamp` it's irrelevant /// whether the range is inclusive or not, the output is the same. +/// /// - The result of each if statement must be equal to the argument unique to that if statement. The /// result can not be the shared argument in either case. fn is_clamp_meta_pattern<'tcx>( diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index cd61e733694bb..da8c918a62bb6 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_lint_allowed, path_to_local, search_same, SpanlessEq, SpanlessHash}; use core::cmp::Ordering; use core::{iter, slice}; @@ -9,9 +9,9 @@ use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatKind, RangeEnd}; use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; -use rustc_span::{ErrorGuaranteed, Symbol}; +use rustc_span::{ErrorGuaranteed, Span, Symbol}; use super::MATCH_SAME_ARMS; @@ -110,20 +110,22 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { && check_same_body() }; + let mut appl = Applicability::MaybeIncorrect; let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) { if matches!(arm2.pat.kind, PatKind::Wild) { if !cx.tcx.features().non_exhaustive_omitted_patterns_lint || is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id) { + let arm_span = adjusted_arm_span(cx, arm1.span); span_lint_hir_and_then( cx, MATCH_SAME_ARMS, arm1.hir_id, - arm1.span, + arm_span, "this match arm has an identical body to the `_` wildcard arm", |diag| { - diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect) + diag.span_suggestion(arm_span, "try removing the arm", "", appl) .help("or try changing either arm body") .span_note(arm2.span, "`_` wildcard arm here"); }, @@ -144,23 +146,36 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { keep_arm.span, "this match arm has an identical body to another arm", |diag| { - let move_pat_snip = snippet(cx, move_arm.pat.span, ""); - let keep_pat_snip = snippet(cx, keep_arm.pat.span, ""); + let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "", &mut appl); + let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "", &mut appl); diag.span_suggestion( keep_arm.pat.span, - "try merging the arm patterns", + "or try merging the arm patterns", format!("{keep_pat_snip} | {move_pat_snip}"), - Applicability::MaybeIncorrect, + appl, ) - .help("or try changing either arm body") - .span_note(move_arm.span, "other arm here"); + .span_suggestion( + adjusted_arm_span(cx, move_arm.span), + "and remove this obsolete arm", + "", + appl, + ) + .help("try changing either arm body"); }, ); } } } +/// Extend arm's span to include the comma and whitespaces after it. +fn adjusted_arm_span(cx: &LateContext<'_>, span: Span) -> Span { + let source_map = cx.sess().source_map(); + source_map + .span_extend_while(span, |c| c == ',' || c.is_ascii_whitespace()) + .unwrap_or(span) +} + #[derive(Clone, Copy)] enum NormalizedPat<'a> { Wild, diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index f775ea072e1f0..6ac0705abb212 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -115,45 +115,60 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } - fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> { - self.cx.typeck_results().expr_ty(ex) + fn is_sig_drop_expr(&mut self, ex: &'tcx Expr<'_>) -> bool { + !ex.is_syntactic_place_expr() && self.has_sig_drop_attr(self.cx.typeck_results().expr_ty(ex)) } - fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool { - !self.seen_types.insert(ty) + fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + self.seen_types.clear(); + self.has_sig_drop_attr_impl(ty) } - fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() { - if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 { + if get_attr( + self.cx.sess(), + self.cx.tcx.get_attrs_unchecked(adt.did()), + "has_significant_drop", + ) + .count() + > 0 + { return true; } } - match ty.kind() { - rustc_middle::ty::Adt(a, b) => { - for f in a.all_fields() { - let ty = f.ty(cx.tcx, b); - if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) { - return true; - } - } + if !self.seen_types.insert(ty) { + return false; + } - for generic_arg in *b { - if let GenericArgKind::Type(ty) = generic_arg.unpack() { - if self.has_sig_drop_attr(cx, ty) { - return true; - } - } - } - false + let result = match ty.kind() { + rustc_middle::ty::Adt(adt, args) => { + // if some field has significant drop, + adt.all_fields() + .map(|field| field.ty(self.cx.tcx, args)) + .any(|ty| self.has_sig_drop_attr_impl(ty)) + // or if there is no generic lifetime and.. + // (to avoid false positive on `Ref<'a, MutexGuard>`) + || (args + .iter() + .all(|arg| !matches!(arg.unpack(), GenericArgKind::Lifetime(_))) + // some generic parameter has significant drop + // (to avoid false negative on `Box>`) + && args + .iter() + .filter_map(|arg| match arg.unpack() { + GenericArgKind::Type(ty) => Some(ty), + _ => None, + }) + .any(|ty| self.has_sig_drop_attr_impl(ty))) }, - rustc_middle::ty::Array(ty, _) - | rustc_middle::ty::RawPtr(ty, _) - | rustc_middle::ty::Ref(_, ty, _) - | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty), + rustc_middle::ty::Tuple(tys) => tys.iter().any(|ty| self.has_sig_drop_attr_impl(ty)), + rustc_middle::ty::Array(ty, _) | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr_impl(*ty), _ => false, - } + }; + + result } } @@ -232,7 +247,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { if self.current_sig_drop.is_some() { return; } - let ty = self.sig_drop_checker.get_type(expr); + let ty = self.cx.typeck_results().expr_ty(expr); if ty.is_ref() { // We checked that the type was ref, so builtin_deref will return Some, // but let's avoid any chance of an ICE. @@ -279,11 +294,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - if !self.is_chain_end - && self - .sig_drop_checker - .has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex)) - { + if !self.is_chain_end && self.sig_drop_checker.is_sig_drop_expr(ex) { self.has_significant_drop = true; return; } @@ -387,10 +398,7 @@ fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<' impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { - if self - .sig_drop_checker - .has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex)) - { + if self.sig_drop_checker.is_sig_drop_expr(ex) { self.found_sig_drop_spans.insert(ex.span); return; } diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index fba76852344f5..c9f56e1d98097 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node}; +use clippy_utils::macros::{format_args_inputs_span, root_macro_call_first_node, FormatArgsStorage}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; use rustc_errors::Applicability; @@ -16,6 +16,7 @@ use super::EXPECT_FUN_CALL; #[allow(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, + format_args_storage: &FormatArgsStorage, expr: &hir::Expr<'_>, method_span: Span, name: &str, @@ -134,9 +135,9 @@ pub(super) fn check<'tcx>( // Special handling for `format!` as arg_root if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) { if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) - && let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn) + && let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn) { - let span = format_args_inputs_span(&format_args); + let span = format_args_inputs_span(format_args); let sugg = snippet_with_applicability(cx, span, "..", &mut applicability); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs index 12647ea1ffcb3..b93d51eac647b 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints/src/methods/iter_filter.rs @@ -126,15 +126,15 @@ enum FilterType { /// /// How this is done: /// 1. we know that this is invoked in a method call with `filter` as the method name via `mod.rs` -/// 2. we check that we are in a trait method. Therefore we are in an -/// `(x as Iterator).filter({filter_arg})` method call. +/// 2. we check that we are in a trait method. Therefore we are in an `(x as +/// Iterator).filter({filter_arg})` method call. /// 3. we check that the parent expression is not a map. This is because we don't want to lint /// twice, and we already have a specialized lint for that. /// 4. we check that the span of the filter does not contain a comment. /// 5. we get the type of the `Item` in the `Iterator`, and compare against the type of Option and -/// Result. +/// Result. /// 6. we finally check the contents of the filter argument to see if it is a call to `is_some` or -/// `is_ok`. +/// `is_ok`. /// 7. if all of the above are true, then we return the `FilterType` fn expression_type( cx: &LateContext<'_>, diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index 7c852a3768d16..05e77386128f9 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -12,8 +12,10 @@ use rustc_middle::ty; use rustc_span::{sym, Span}; /// lint use of: +/// /// - `hashmap.iter().map(|(_, v)| v)` /// - `hashmap.into_iter().map(|(_, v)| v)` +/// /// on `HashMaps` and `BTreeMaps` in std pub(super) fn check<'tcx>( diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index a52d38790a2bd..5ccb5243e903c 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -69,12 +69,9 @@ pub(super) fn check<'tcx>( used_move: HirIdSet::default(), }; - ExprUseVisitor::for_clippy( - cx, - closure.def_id, - &mut delegate, - ) - .consume_body(body).into_ok(); + ExprUseVisitor::for_clippy(cx, closure.def_id, &mut delegate) + .consume_body(body) + .into_ok(); let mut to_be_discarded = false; diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 2b92bff016db3..9d67aa2337979 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -133,6 +133,7 @@ use bind_instead_of_map::BindInsteadOfMap; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use clippy_utils::macros::FormatArgsStorage; use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item}; use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty}; pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES; @@ -4087,12 +4088,14 @@ declare_clippy_lint! { suspicious, "is_empty() called on strings known at compile time" } + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, allow_expect_in_tests: bool, allow_unwrap_in_tests: bool, allowed_dotfiles: FxHashSet, + format_args: FormatArgsStorage, } impl Methods { @@ -4103,6 +4106,7 @@ impl Methods { allow_expect_in_tests: bool, allow_unwrap_in_tests: bool, mut allowed_dotfiles: FxHashSet, + format_args: FormatArgsStorage, ) -> Self { allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES.iter().map(ToString::to_string)); @@ -4112,6 +4116,7 @@ impl Methods { allow_expect_in_tests, allow_unwrap_in_tests, allowed_dotfiles, + format_args, } } } @@ -4281,7 +4286,15 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args); - expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args); + expect_fun_call::check( + cx, + &self.format_args, + expr, + method_span, + method_call.ident.as_str(), + receiver, + args, + ); clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args); clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args); inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args); diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 520dcb2d52dcb..7431dc1cf0b16 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -3,10 +3,12 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::ForLoop; use clippy_utils::source::snippet_opt; use clippy_utils::ty::{get_iterator_item_ty, implements_trait}; -use clippy_utils::{fn_def_id, get_parent_expr}; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local}; +use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind}; use rustc_lint::LateContext; use rustc_span::{sym, Symbol}; @@ -40,6 +42,53 @@ pub fn check_for_loop_iter( && !clone_or_copy_needed && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) { + // Issue 12098 + // https://github.com/rust-lang/rust-clippy/issues/12098 + // if the assignee have `mut borrow` conflict with the iteratee + // the lint should not execute, former didn't consider the mut case + + // check whether `expr` is mutable + fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let Some(hir_id) = path_to_local(expr) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + { + matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) + } else { + true + } + } + + fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool { + let mut change = false; + if let ExprKind::Block(block, ..) = body.kind { + for_each_expr(block, |e| { + match e.kind { + ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => { + change |= !can_mut_borrow_both(cx, caller, assignee); + }, + _ => {}, + } + // the return value has no effect but the function need one return value + ControlFlow::<()>::Continue(()) + }); + } + change + } + + if let ExprKind::Call(_, [child, ..]) = expr.kind { + // filter first layer of iterator + let mut child = child; + // get inner real caller requests for clone + while let ExprKind::MethodCall(_, caller, _, _) = child.kind { + child = caller; + } + if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) { + // skip lint + return true; + } + }; + + // the lint should not be executed if no violation happens let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind && maybe_iter_method_name.ident.name == sym::iter && let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator) diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index c50f24f824ab0..34d7b9acbe4b0 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -120,6 +120,7 @@ fn pat_bindings(pat: &Pat<'_>) -> Vec { /// operations performed on `binding_hir_ids` are: /// * to take non-mutable references to them /// * to use them as non-mutable `&self` in method calls +/// /// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true /// when `CloneOrCopyVisitor` is done visiting. struct CloneOrCopyVisitor<'cx, 'tcx> { diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index ae6cf992ef7b4..daf166bad90d8 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap}; +use clippy_utils::mir::PossibleBorrowerMap; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::{expr_use_ctxt, peel_n_hir_expr_refs, DefinedTy, ExprUseNode}; @@ -11,7 +11,6 @@ use rustc_hir::{Body, Expr, ExprKind, Mutability, Path, QPath}; use rustc_index::bit_set::BitSet; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::mir::{Rvalue, StatementKind}; use rustc_middle::ty::{ self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty, }; @@ -106,7 +105,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { } && let count = needless_borrow_count( cx, - &mut self.possible_borrowers, fn_id, cx.typeck_results().node_args(hir_id), i, @@ -155,11 +153,9 @@ fn path_has_args(p: &QPath<'_>) -> bool { /// The following constraints will be checked: /// * The borrowed expression meets all the generic type's constraints. /// * The generic type appears only once in the functions signature. -/// * The borrowed value will not be moved if it is used later in the function. -#[expect(clippy::too_many_arguments)] +/// * The borrowed value is Copy itself OR not a variable (created by a function call) fn needless_borrow_count<'tcx>( cx: &LateContext<'tcx>, - possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, fn_id: DefId, callee_args: ty::GenericArgsRef<'tcx>, arg_index: usize, @@ -234,9 +230,9 @@ fn needless_borrow_count<'tcx>( let referent_ty = cx.typeck_results().expr_ty(referent); - if !is_copy(cx, referent_ty) - && (referent_ty.has_significant_drop(cx.tcx, cx.param_env) - || !referent_used_exactly_once(cx, possible_borrowers, reference)) + if (!is_copy(cx, referent_ty) && !referent_ty.is_ref()) + && let ExprKind::AddrOf(_, _, inner) = reference.kind + && !matches!(inner.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) { return false; } @@ -339,37 +335,6 @@ fn is_mixed_projection_predicate<'tcx>( } } -fn referent_used_exactly_once<'tcx>( - cx: &LateContext<'tcx>, - possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>, - reference: &Expr<'tcx>, -) -> bool { - if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id) - && let Some(local) = expr_local(cx.tcx, reference) - && let [location] = *local_assignments(mir, local).as_slice() - && let block_data = &mir.basic_blocks[location.block] - && let Some(statement) = block_data.statements.get(location.statement_index) - && let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind - && !place.is_indirect_first_projection() - { - let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id); - if possible_borrowers - .last() - .map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id) - { - possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir))); - } - let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1; - // If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is - // that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of - // itself. See the comment in that method for an explanation as to why. - possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location) - && used_exactly_once(mir, place.local).unwrap_or(false) - } else { - false - } -} - // Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting // projected type that is a type parameter. Returns `false` if replacing the types would have an // effect on the function signature beyond substituting `new_ty` for `param_ty`. @@ -408,7 +373,11 @@ fn replace_types<'tcx>( && let Some(term_ty) = projection_predicate.term.ty() && let ty::Param(term_param_ty) = term_ty.kind() { - let projection = projection_predicate.projection_term.with_self_ty(cx.tcx, new_ty).expect_ty(cx.tcx).to_ty(cx.tcx); + let projection = projection_predicate + .projection_term + .with_self_ty(cx.tcx, new_ty) + .expect_ty(cx.tcx) + .to_ty(cx.tcx); if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection) && args[term_param_ty.index as usize] != GenericArg::from(projected_ty) diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index 8b4a12bb76648..b97cb4579ca7b 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -178,8 +178,7 @@ impl EarlyLintPass for NeedlessContinue { /// Given an expression, returns true if either of the following is true /// /// - The expression is a `continue` node. -/// - The expression node is a block with the first statement being a -/// `continue`. +/// - The expression node is a block with the first statement being a `continue`. fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool { match else_expr.kind { ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label), diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 6605d1fa51a36..5a0ae1a4d6d29 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -273,24 +273,16 @@ fn check<'tcx>( msg_span, "unneeded late initialization", |diag| { - diag.tool_only_span_suggestion( - local_stmt.span, - "remove the local", - "", - Applicability::MachineApplicable, - ); - - diag.span_suggestion( - assign.lhs_span, - format!("declare `{binding_name}` here"), - let_snippet, + diag.multipart_suggestion( + format!("move the declaration `{binding_name}` here"), + vec![(local_stmt.span, String::new()), (assign.lhs_span, let_snippet)], Applicability::MachineApplicable, ); }, ); }, ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => { - let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?; + let (applicability, mut suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?; span_lint_and_then( cx, @@ -298,30 +290,26 @@ fn check<'tcx>( local_stmt.span, "unneeded late initialization", |diag| { - diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability); - - diag.span_suggestion_verbose( - usage.stmt.span.shrink_to_lo(), - format!("declare `{binding_name}` here"), - format!("{let_snippet} = "), - applicability, - ); - - diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability); + suggestions.push((local_stmt.span, String::new())); + suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = "))); if usage.needs_semi { - diag.span_suggestion( - usage.stmt.span.shrink_to_hi(), - "add a semicolon after the `if` expression", - ";", - applicability, - ); + suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned())); } + + diag.multipart_suggestion( + format!( + "move the declaration `{binding_name}` here and remove the assignments from the branches" + ), + suggestions, + applicability, + ); }, ); }, ExprKind::Match(_, arms, MatchSource::Normal) => { - let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?; + let (applicability, mut suggestions) = + assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?; span_lint_and_then( cx, @@ -329,29 +317,18 @@ fn check<'tcx>( local_stmt.span, "unneeded late initialization", |diag| { - diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability); + suggestions.push((local_stmt.span, String::new())); + suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = "))); - diag.span_suggestion_verbose( - usage.stmt.span.shrink_to_lo(), - format!("declare `{binding_name}` here"), - format!("{let_snippet} = "), - applicability, - ); + if usage.needs_semi { + suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned())); + } diag.multipart_suggestion( - "remove the assignments from the `match` arms", + format!("move the declaration `{binding_name}` here and remove the assignments from the `match` arms"), suggestions, applicability, ); - - if usage.needs_semi { - diag.span_suggestion( - usage.stmt.span.shrink_to_hi(), - "add a semicolon after the `match` expression", - ";", - applicability, - ); - } }, ); }, diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 9b852f52ea1eb..da6ed5fb96f12 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -117,7 +117,9 @@ fn check_closures<'tcx>( .associated_body() .map(|(_, body_id)| hir.body(body_id)) { - euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx).consume_body(body).into_ok(); + euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx) + .consume_body(body) + .into_ok(); } } } @@ -194,7 +196,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { async_closures: FxHashSet::default(), tcx: cx.tcx, }; - euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx).consume_body(body).into_ok(); + euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx) + .consume_body(body) + .into_ok(); let mut checked_closures = FxHashSet::default(); diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 0986571d0f281..f2e00cef7e9ff 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -133,7 +133,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { // function body. let MovedVariablesCtxt { moved_vars } = { let mut ctx = MovedVariablesCtxt::default(); - euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx).consume_body(body).into_ok(); + euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx) + .consume_body(body) + .into_ok(); ctx }; diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index f915145e794bc..87f886b1128d8 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -94,7 +94,6 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect { fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) { for hir_id in self.local_bindings.pop().unwrap() { - // FIXME(rust/#120456) - is `swap_remove` correct? if let Some(span) = self.underscore_bindings.swap_remove(&hir_id) { span_lint_hir( cx, @@ -109,7 +108,6 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect { fn check_expr(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let Some(def_id) = path_to_local(expr) { - // FIXME(rust/#120456) - is `swap_remove` correct? self.underscore_bindings.swap_remove(&def_id); } } @@ -118,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect { impl NoEffect { fn check_no_effect(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool { if let StmtKind::Semi(expr) = stmt.kind { - // move `expr.span.from_expansion()` ahead + // Covered by rustc `path_statements` lint + if matches!(expr.kind, ExprKind::Path(_)) { + return true; + } + if expr.span.from_expansion() { return false; } diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index ef51a9a9a1c8a..75066c1f0d2e2 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -1,8 +1,14 @@ use clippy_utils::diagnostics::span_lint; +use clippy_utils::is_in_test; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; + +#[derive(Clone)] +pub struct PanicUnimplemented { + pub allow_panic_in_tests: bool, +} declare_clippy_lint! { /// ### What it does @@ -77,7 +83,7 @@ declare_clippy_lint! { "usage of the `unreachable!` macro" } -declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]); +impl_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]); impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -85,7 +91,9 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { return; }; if is_panic(cx, macro_call.def_id) { - if cx.tcx.hir().is_inside_const_context(expr.hir_id) { + if cx.tcx.hir().is_inside_const_context(expr.hir_id) + || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) + { return; } diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 87a3c3874d77b..292124196ff64 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -389,6 +389,10 @@ declare_lint_pass!(StrToString => [STR_TO_STRING]); impl<'tcx> LateLintPass<'tcx> for StrToString { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind && path.ident.name == sym::to_string && let ty = cx.typeck_results().expr_ty(self_arg) @@ -437,6 +441,10 @@ declare_lint_pass!(StringToString => [STRING_TO_STRING]); impl<'tcx> LateLintPass<'tcx> for StringToString { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind && path.ident.name == sym::to_string && let ty = cx.typeck_results().expr_ty(self_arg) diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index 7d824ef213900..3729dfd3e86f2 100644 --- a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -28,7 +28,7 @@ pub(super) fn check<'tcx>( let int_ty = substs.type_at(0); if from_ty != int_ty { - return false; + return false; } span_lint_and_then( diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 5b2841dcd8330..c0d9bcdd259c7 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -251,11 +251,7 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> { local_id: unwrap_info.local_id, }; - let vis = ExprUseVisitor::for_clippy( - self.cx, - cond.hir_id.owner.def_id, - &mut delegate, - ); + let vis = ExprUseVisitor::for_clippy(self.cx, cond.hir_id.owner.def_id, &mut delegate); vis.walk_expr(cond).into_ok(); vis.walk_expr(branch).into_ok(); diff --git a/clippy_lints/src/utils/format_args_collector.rs b/clippy_lints/src/utils/format_args_collector.rs index 58e66c9f9b951..5acfd35fd6ae6 100644 --- a/clippy_lints/src/utils/format_args_collector.rs +++ b/clippy_lints/src/utils/format_args_collector.rs @@ -1,4 +1,4 @@ -use clippy_utils::macros::AST_FORMAT_ARGS; +use clippy_utils::macros::FormatArgsStorage; use clippy_utils::source::snippet_opt; use itertools::Itertools; use rustc_ast::{Crate, Expr, ExprKind, FormatArgs}; @@ -9,13 +9,20 @@ use rustc_session::impl_lint_pass; use rustc_span::{hygiene, Span}; use std::iter::once; use std::mem; -use std::rc::Rc; -/// Collects [`rustc_ast::FormatArgs`] so that future late passes can call -/// [`clippy_utils::macros::find_format_args`] -#[derive(Default)] +/// Populates [`FormatArgsStorage`] with AST [`FormatArgs`] nodes pub struct FormatArgsCollector { - format_args: FxHashMap>, + format_args: FxHashMap, + storage: FormatArgsStorage, +} + +impl FormatArgsCollector { + pub fn new(storage: FormatArgsStorage) -> Self { + Self { + format_args: FxHashMap::default(), + storage, + } + } } impl_lint_pass!(FormatArgsCollector => []); @@ -27,16 +34,12 @@ impl EarlyLintPass for FormatArgsCollector { return; } - self.format_args - .insert(expr.span.with_parent(None), Rc::new((**args).clone())); + self.format_args.insert(expr.span.with_parent(None), (**args).clone()); } } fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) { - AST_FORMAT_ARGS.with(|ast_format_args| { - let result = ast_format_args.set(mem::take(&mut self.format_args)); - debug_assert!(result.is_ok()); - }); + self.storage.set(mem::take(&mut self.format_args)); } } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 26c6859233d53..ff6ee0d10ad56 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall}; +use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall}; use clippy_utils::source::{expand_past_previous_comma, snippet_opt}; use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_ast::token::LitKind; @@ -236,13 +236,15 @@ declare_clippy_lint! { #[derive(Default)] pub struct Write { + format_args: FormatArgsStorage, in_debug_impl: bool, allow_print_in_tests: bool, } impl Write { - pub fn new(allow_print_in_tests: bool) -> Self { + pub fn new(format_args: FormatArgsStorage, allow_print_in_tests: bool) -> Self { Self { + format_args, allow_print_in_tests, ..Default::default() } @@ -307,7 +309,7 @@ impl<'tcx> LateLintPass<'tcx> for Write { _ => return, } - if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) { + if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { // ignore `writeln!(w)` and `write!(v, some_macro!())` if format_args.span.from_expansion() { return; @@ -315,15 +317,15 @@ impl<'tcx> LateLintPass<'tcx> for Write { match diag_name { sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, &format_args, ¯o_call, name); + check_newline(cx, format_args, ¯o_call, name); }, sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, &format_args, ¯o_call, name); + check_empty_string(cx, format_args, ¯o_call, name); }, _ => {}, } - check_literal(cx, &format_args, name); + check_literal(cx, format_args, name); if !self.in_debug_impl { for piece in &format_args.template { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 99d7aba2f7a15..4c603bda770a9 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -193,6 +193,21 @@ pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option< None } +/// Checks if the given local has an initializer or is from something other than a `let` statement +/// +/// e.g. returns true for `x` in `fn f(x: usize) { .. }` and `let x = 1;` but false for `let x;` +pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool { + for (_, node) in cx.tcx.hir().parent_iter(local) { + match node { + Node::Pat(..) | Node::PatField(..) => {}, + Node::LetStmt(let_stmt) => return let_stmt.init.is_some(), + _ => return true, + } + } + + false +} + /// Returns `true` if the given `NodeId` is inside a constant context /// /// # Example @@ -1499,15 +1514,18 @@ pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool { } /// Checks whether the given `Expr` is a range equivalent to a `RangeFull`. +/// /// For the lower bound, this means that: /// - either there is none /// - or it is the smallest value that can be represented by the range's integer type +/// /// For the upper bound, this means that: /// - either there is none /// - or it is the largest value that can be represented by the range's integer type and is /// inclusive /// - or it is a call to some container's `len` method and is exclusive, and the range is passed to /// a method call on that same container (e.g. `v.drain(..v.len())`) +/// /// If the given `Expr` is not some kind of range, the function returns `false`. pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool { let ty = cx.typeck_results().expr_ty(expr); diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 257dd76ab15cf..8daab9b0d92cf 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -5,15 +5,13 @@ use crate::visitors::{for_each_expr, Descend}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sync::{Lrc, OnceLock}; use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath}; use rustc_lint::LateContext; use rustc_span::def_id::DefId; use rustc_span::hygiene::{self, MacroKind, SyntaxContext}; use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol}; -use std::cell::OnceCell; use std::ops::ControlFlow; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[ sym::assert_eq_macro, @@ -388,50 +386,44 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> } } -thread_local! { - /// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be - /// able to access the many features of a [`LateContext`]. - /// - /// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an - /// assumption that the early pass that populates the map and the later late passes will all be - /// running on the same thread. - #[doc(hidden)] - pub static AST_FORMAT_ARGS: OnceCell>> = { - static CALLED: AtomicBool = AtomicBool::new(false); - debug_assert!( - !CALLED.swap(true, Ordering::SeqCst), - "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread", - ); - - OnceCell::new() - }; -} +/// Stores AST [`FormatArgs`] nodes for use in late lint passes, as they are in a desugared form in +/// the HIR +#[derive(Default, Clone)] +pub struct FormatArgsStorage(Lrc>>); -/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of -/// `expn_id` -pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option> { - let format_args_expr = for_each_expr(start, |expr| { - let ctxt = expr.span.ctxt(); - if ctxt.outer_expn().is_descendant_of(expn_id) { - if macro_backtrace(expr.span) - .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) - .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)) - { - ControlFlow::Break(expr) +impl FormatArgsStorage { + /// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of + /// `expn_id` + /// + /// See also [`find_format_arg_expr`] + pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> { + let format_args_expr = for_each_expr(start, |expr| { + let ctxt = expr.span.ctxt(); + if ctxt.outer_expn().is_descendant_of(expn_id) { + if macro_backtrace(expr.span) + .map(|macro_call| cx.tcx.item_name(macro_call.def_id)) + .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl)) + { + ControlFlow::Break(expr) + } else { + ControlFlow::Continue(Descend::Yes) + } } else { - ControlFlow::Continue(Descend::Yes) + ControlFlow::Continue(Descend::No) } - } else { - ControlFlow::Continue(Descend::No) - } - })?; + })?; - AST_FORMAT_ARGS.with(|ast_format_args| { - ast_format_args - .get()? - .get(&format_args_expr.span.with_parent(None)) - .cloned() - }) + debug_assert!(self.0.get().is_some(), "`FormatArgsStorage` not yet populated"); + + self.0.get()?.get(&format_args_expr.span.with_parent(None)) + } + + /// Should only be called by `FormatArgsCollector` + pub fn set(&self, format_args: FxHashMap) { + self.0 + .set(format_args) + .expect("`FormatArgsStorage::set` should only be called once"); + } } /// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index ff9f06531ea11..8ee7d87acb3e0 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -3,7 +3,7 @@ // of terminologies might not be relevant in the context of Clippy. Note that its behavior might // differ from the time of `rustc` even if the name stays the same. -use clippy_config::msrvs::Msrv; +use clippy_config::msrvs::{self, Msrv}; use hir::LangItem; use rustc_attr::StableSince; use rustc_const_eval::transform::check_consts::ConstCx; @@ -42,7 +42,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) for bb in &*body.basic_blocks { check_terminator(tcx, body, bb.terminator(), msrv)?; for stmt in &bb.statements { - check_statement(tcx, body, def_id, stmt)?; + check_statement(tcx, body, def_id, stmt, msrv)?; } } Ok(()) @@ -102,13 +102,14 @@ fn check_rvalue<'tcx>( def_id: DefId, rvalue: &Rvalue<'tcx>, span: Span, + msrv: &Msrv, ) -> McfResult { match rvalue { Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())), Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => { - check_place(tcx, *place, span, body) + check_place(tcx, *place, span, body, msrv) }, - Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body), + Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body, msrv), Rvalue::Repeat(operand, _) | Rvalue::Use(operand) | Rvalue::Cast( @@ -122,7 +123,7 @@ fn check_rvalue<'tcx>( | CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer), operand, _, - ) => check_operand(tcx, operand, span, body), + ) => check_operand(tcx, operand, span, body, msrv), Rvalue::Cast( CastKind::PointerCoercion( PointerCoercion::UnsafeFnPointer @@ -133,15 +134,13 @@ fn check_rvalue<'tcx>( _, ) => Err((span, "function pointer casts are not allowed in const fn".into())), Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), op, cast_ty) => { - let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) { - deref_ty - } else { + let Some(pointee_ty) = cast_ty.builtin_deref(true) else { // We cannot allow this for now. return Err((span, "unsizing casts are only allowed for references right now".into())); }; let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id)); if let ty::Slice(_) | ty::Str = unsized_ty.kind() { - check_operand(tcx, op, span, body)?; + check_operand(tcx, op, span, body, msrv)?; // Casting/coercing things to slices is fine. Ok(()) } else { @@ -162,8 +161,8 @@ fn check_rvalue<'tcx>( )), // binops are fine on integers Rvalue::BinaryOp(_, box (lhs, rhs)) => { - check_operand(tcx, lhs, span, body)?; - check_operand(tcx, rhs, span, body)?; + check_operand(tcx, lhs, span, body, msrv)?; + check_operand(tcx, rhs, span, body, msrv)?; let ty = lhs.ty(body, tcx); if ty.is_integral() || ty.is_bool() || ty.is_char() { Ok(()) @@ -179,14 +178,14 @@ fn check_rvalue<'tcx>( Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, tcx); if ty.is_integral() || ty.is_bool() { - check_operand(tcx, operand, span, body) + check_operand(tcx, operand, span, body, msrv) } else { Err((span, "only int and `bool` operations are stable in const fn".into())) } }, Rvalue::Aggregate(_, operands) => { for operand in operands { - check_operand(tcx, operand, span, body)?; + check_operand(tcx, operand, span, body, msrv)?; } Ok(()) }, @@ -198,28 +197,29 @@ fn check_statement<'tcx>( body: &Body<'tcx>, def_id: DefId, statement: &Statement<'tcx>, + msrv: &Msrv, ) -> McfResult { let span = statement.source_info.span; match &statement.kind { StatementKind::Assign(box (place, rval)) => { - check_place(tcx, *place, span, body)?; - check_rvalue(tcx, body, def_id, rval, span) + check_place(tcx, *place, span, body, msrv)?; + check_rvalue(tcx, body, def_id, rval, span, msrv) }, - StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body), + StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body, msrv), // just an assignment StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => { - check_place(tcx, **place, span, body) + check_place(tcx, **place, span, body, msrv) }, - StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body), + StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body, msrv), StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping( rustc_middle::mir::CopyNonOverlapping { dst, src, count }, )) => { - check_operand(tcx, dst, span, body)?; - check_operand(tcx, src, span, body)?; - check_operand(tcx, count, span, body) + check_operand(tcx, dst, span, body, msrv)?; + check_operand(tcx, src, span, body, msrv)?; + check_operand(tcx, count, span, body, msrv) }, // These are all NOPs StatementKind::StorageLive(_) @@ -233,7 +233,13 @@ fn check_statement<'tcx>( } } -fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { +fn check_operand<'tcx>( + tcx: TyCtxt<'tcx>, + operand: &Operand<'tcx>, + span: Span, + body: &Body<'tcx>, + msrv: &Msrv, +) -> McfResult { match operand { Operand::Move(place) => { if !place.projection.as_ref().is_empty() @@ -245,9 +251,9 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b )); } - check_place(tcx, *place, span, body) + check_place(tcx, *place, span, body, msrv) }, - Operand::Copy(place) => check_place(tcx, *place, span, body), + Operand::Copy(place) => check_place(tcx, *place, span, body, msrv), Operand::Constant(c) => match c.check_static_ptr(tcx) { Some(_) => Err((span, "cannot access `static` items in const fn".into())), None => Ok(()), @@ -255,23 +261,27 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b } } -fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult { +fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>, msrv: &Msrv) -> McfResult { for (base, elem) in place.as_ref().iter_projections() { match elem { ProjectionElem::Field(..) => { - let base_ty = base.ty(body, tcx).ty; - if let Some(def) = base_ty.ty_adt_def() { - // No union field accesses in `const fn` - if def.is_union() { - return Err((span, "accessing union fields is unstable".into())); - } + if base.ty(body, tcx).ty.is_union() && !msrv.meets(msrvs::CONST_FN_UNION) { + return Err((span, "accessing union fields is unstable".into())); } }, + ProjectionElem::Deref => match base.ty(body, tcx).ty.kind() { + ty::RawPtr(_, hir::Mutability::Mut) => { + return Err((span, "dereferencing raw mut pointer in const fn is unstable".into())); + }, + ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(msrvs::CONST_RAW_PTR_DEREF) => { + return Err((span, "dereferencing raw const pointer in const fn is unstable".into())); + }, + _ => (), + }, ProjectionElem::ConstantIndex { .. } | ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } - | ProjectionElem::Deref | ProjectionElem::Subtype(_) | ProjectionElem::Index(_) => {}, } @@ -304,7 +314,7 @@ fn check_terminator<'tcx>( } Ok(()) }, - TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body), + TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body, msrv), TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } => { Err((span, "const fn coroutines are unstable".into())) }, @@ -341,10 +351,10 @@ fn check_terminator<'tcx>( )); } - check_operand(tcx, func, span, body)?; + check_operand(tcx, func, span, body, msrv)?; for arg in args { - check_operand(tcx, &arg.node, span, body)?; + check_operand(tcx, &arg.node, span, body, msrv)?; } Ok(()) } else { @@ -357,7 +367,7 @@ fn check_terminator<'tcx>( msg: _, target: _, unwind: _, - } => check_operand(tcx, cond, span, body), + } => check_operand(tcx, cond, span, body, msrv), TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())), } } diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index e72467edeeb0d..fd67e039c29a9 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -250,7 +250,7 @@ pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow< /// - Applicability level `Unspecified` will never be changed. /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. /// - If the default value is used and the applicability level is `MachineApplicable`, change it to -/// `HasPlaceholders` +/// `HasPlaceholders` pub fn snippet_with_applicability<'a, T: LintContext>( cx: &T, span: Span, diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index bf03c6c16015c..6319c7bfa6b81 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -67,8 +67,7 @@ impl<'a> Sugg<'a> { /// - Applicability level `Unspecified` will never be changed. /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`. /// - If the default value is used and the applicability level is `MachineApplicable`, change it - /// to - /// `HasPlaceholders` + /// to `HasPlaceholders` pub fn hir_with_applicability( cx: &LateContext<'_>, expr: &hir::Expr<'_>, diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index c29e3feac9ad5..2dacc34867f07 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -273,11 +273,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( let infcx = tcx.infer_ctxt().build(); let args = args .into_iter() - .map(|arg| { - arg.into().unwrap_or_else(|| { - infcx.next_ty_var(DUMMY_SP).into() - }) - }) + .map(|arg| arg.into().unwrap_or_else(|| infcx.next_ty_var(DUMMY_SP).into())) .collect::>(); // If an effect arg was not specified, we need to specify it. @@ -795,7 +791,8 @@ fn sig_from_bounds<'tcx>( inputs = Some(i); }, ty::ClauseKind::Projection(p) - if Some(p.projection_term.def_id) == lang_items.fn_once_output() && p.projection_term.self_ty() == ty => + if Some(p.projection_term.def_id) == lang_items.fn_once_output() + && p.projection_term.self_ty() == ty => { if output.is_some() { // Multiple different fn trait impls. Is this even allowed? @@ -956,11 +953,7 @@ pub struct AdtVariantInfo { impl AdtVariantInfo { /// Returns ADT variants ordered by size - pub fn new<'tcx>( - cx: &LateContext<'tcx>, - adt: AdtDef<'tcx>, - subst: GenericArgsRef<'tcx> - ) -> Vec { + pub fn new<'tcx>(cx: &LateContext<'tcx>, adt: AdtDef<'tcx>, subst: GenericArgsRef<'tcx>) -> Vec { let mut variants_size = adt .variants() .iter() diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 9abb4ef9b8d3a..2a25d51d8e509 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -16,13 +16,9 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> used_mutably: HirIdSet::default(), skip: false, }; - ExprUseVisitor::for_clippy( - cx, - expr.hir_id.owner.def_id, - &mut delegate, - ) - .walk_expr(expr) - .into_ok(); + ExprUseVisitor::for_clippy(cx, expr.hir_id.owner.def_id, &mut delegate) + .walk_expr(expr) + .into_ok(); if delegate.skip { return None; diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index a828d12370467..8c5a409e25b15 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -13,7 +13,7 @@ default-run = "lintcheck" [dependencies] anyhow = "1.0.69" cargo_metadata = "0.15.3" -clap = { version = "4.1.8", features = ["derive", "env"] } +clap = { version = "4.4", features = ["derive", "env"] } crates_io_api = "0.8.1" crossbeam-channel = "0.5.6" flate2 = "1.0" diff --git a/rust-toolchain b/rust-toolchain index 055f305eb8e18..a0585ffdb45b0 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-05-02" +channel = "nightly-2024-05-16" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr b/tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr index 103e60d84844c..9177e99f8e6e6 100644 --- a/tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr +++ b/tests/ui-cargo/lint_groups_priority/fail/Cargo.stderr @@ -42,4 +42,32 @@ help: to have lints override the group set `pedantic` to a lower priority 19 | pedantic = { level = "warn", priority = -2 } | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -error: could not compile `fail` (lib) due to 3 previous errors +error: lint group `rust_2018_idioms` has the same priority (0) as a lint + --> Cargo.toml:23:1 + | +23 | rust_2018_idioms = "warn" + | ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0 +24 | bare_trait_objects = "allow" + | ------------------ has the same priority as this lint + | + = note: the order of the lints in the table is ignored by Cargo +help: to have lints override the group set `rust_2018_idioms` to a lower priority + | +23 | rust_2018_idioms = { level = "warn", priority = -1 } + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: lint group `pedantic` has the same priority (0) as a lint + --> Cargo.toml:27:1 + | +27 | pedantic = "warn" + | ^^^^^^^^ ------ has an implicit priority of 0 +28 | similar_names = "allow" + | ------------- has the same priority as this lint + | + = note: the order of the lints in the table is ignored by Cargo +help: to have lints override the group set `pedantic` to a lower priority + | +27 | pedantic = { level = "warn", priority = -1 } + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: could not compile `fail` (lib) due to 5 previous errors diff --git a/tests/ui-cargo/lint_groups_priority/fail/Cargo.toml b/tests/ui-cargo/lint_groups_priority/fail/Cargo.toml index 4ce41f7817118..e4d4af9cd234a 100644 --- a/tests/ui-cargo/lint_groups_priority/fail/Cargo.toml +++ b/tests/ui-cargo/lint_groups_priority/fail/Cargo.toml @@ -18,3 +18,11 @@ deprecated = "allow" [lints.clippy] pedantic = { level = "warn", priority = -1 } similar_names = { level = "allow", priority = -1 } + +[workspace.lints.rust] +rust_2018_idioms = "warn" +bare_trait_objects = "allow" + +[workspace.lints.clippy] +pedantic = "warn" +similar_names = "allow" diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs new file mode 100644 index 0000000000000..f5e01b431ad98 --- /dev/null +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.rs @@ -0,0 +1,260 @@ +//! Tests macro_metavars_in_unsafe with default configuration +#![feature(decl_macro, lint_reasons)] +#![warn(clippy::macro_metavars_in_unsafe)] +#![allow(clippy::no_effect)] + +#[macro_export] +macro_rules! allow_works { + ($v:expr) => { + #[expect(clippy::macro_metavars_in_unsafe)] + unsafe { + $v; + }; + }; +} + +#[macro_export] +macro_rules! simple { + ($v:expr) => { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + dbg!($v); + } + }; +} + +#[macro_export] +#[rustfmt::skip] // for some reason rustfmt rewrites $r#unsafe to r#u$nsafe, bug? +macro_rules! raw_symbol { + ($r#mod:expr, $r#unsafe:expr) => { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $r#mod; + } + $r#unsafe; + }; +} + +#[macro_export] +macro_rules! multilevel_unsafe { + ($v:expr) => { + unsafe { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $v; + } + } + }; +} + +#[macro_export] +macro_rules! in_function { + ($v:expr) => { + unsafe { + fn f() { + // function introduces a new body, so don't lint. + $v; + } + } + }; +} + +#[macro_export] +macro_rules! in_function_with_unsafe { + ($v:expr) => { + unsafe { + fn f() { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $v; + } + } + } + }; +} + +#[macro_export] +macro_rules! const_static { + ($c:expr, $s:expr) => { + unsafe { + // const and static introduces new body, don't lint + const _X: i32 = $c; + static _Y: i32 = $s; + } + }; +} + +#[macro_export] +macro_rules! const_generic_in_struct { + ($inside_unsafe:expr, $outside_unsafe:expr) => { + unsafe { + struct Ty< + const L: i32 = 1, + const M: i32 = { + 1; + unsafe { $inside_unsafe } + //~^ ERROR: this macro expands metavariables in an unsafe block + }, + const N: i32 = { $outside_unsafe }, + >; + } + }; +} + +#[macro_export] +macro_rules! fn_with_const_generic { + ($inside_unsafe:expr, $outside_unsafe:expr) => { + unsafe { + fn f() { + $outside_unsafe; + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $inside_unsafe; + } + } + } + }; +} + +#[macro_export] +macro_rules! variables { + ($inside_unsafe:expr, $outside_unsafe:expr) => { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $inside_unsafe; + let inside_unsafe = 1; + inside_unsafe; + } + $outside_unsafe; + let outside_unsafe = 1; + outside_unsafe; + }; +} + +#[macro_export] +macro_rules! multiple_matchers { + ($inside_unsafe:expr, $outside_unsafe:expr) => { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $inside_unsafe; + } + $outside_unsafe; + }; + ($($v:expr, $x:expr),+) => { + $( + $v; + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $x; + } + );+ + }; +} + +#[macro_export] +macro_rules! multiple_unsafe_blocks { + ($w:expr, $x:expr, $y:expr) => { + $w; + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $x; + } + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $x; + $y; + } + }; +} + +pub macro macro2_0($v:expr) { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $v; + } +} + +// don't lint private macros with the default configuration +macro_rules! private_mac { + ($v:expr) => { + unsafe { + $v; + } + }; +} + +// don't lint exported macros that are doc(hidden) because they also aren't part of the public API +#[macro_export] +#[doc(hidden)] +macro_rules! exported_but_hidden { + ($v:expr) => { + unsafe { + $v; + } + }; +} + +// don't lint if the same metavariable is expanded in an unsafe block and then outside of one: +// unsafe {} is still needed at callsite so not problematic +#[macro_export] +macro_rules! does_require_unsafe { + ($v:expr) => { + unsafe { + $v; + } + $v; + }; +} + +#[macro_export] +macro_rules! unsafe_from_root_ctxt { + ($v:expr) => { + // Expands to unsafe { 1 }, but the unsafe block is from the root ctxt and not this macro, + // so no warning. + $v; + }; +} + +// invoked from another macro, should still generate a warning +#[macro_export] +macro_rules! nested_macro_helper { + ($v:expr) => {{ + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + $v; + } + }}; +} + +#[macro_export] +macro_rules! nested_macros { + ($v:expr, $v2:expr) => {{ + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + nested_macro_helper!($v); + $v; + } + }}; +} + +fn main() { + allow_works!(1); + simple!(1); + raw_symbol!(1, 1); + multilevel_unsafe!(1); + in_function!(1); + in_function_with_unsafe!(1); + const_static!(1, 1); + const_generic_in_struct!(1, 1); + fn_with_const_generic!(1, 1); + variables!(1, 1); + multiple_matchers!(1, 1); + multiple_matchers!(1, 1, 1, 1); + macro2_0!(1); + private_mac!(1); + exported_but_hidden!(1); + does_require_unsafe!(1); + multiple_unsafe_blocks!(1, 1, 1); + unsafe_from_root_ctxt!(unsafe { 1 }); + nested_macros!(1, 1); +} diff --git a/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr new file mode 100644 index 0000000000000..d6b97f6fde1e1 --- /dev/null +++ b/tests/ui-toml/macro_metavars_in_unsafe/default/test.stderr @@ -0,0 +1,187 @@ +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:19:9 + | +LL | / unsafe { +LL | | +LL | | dbg!($v); +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:30:9 + | +LL | / unsafe { +LL | | +LL | | $r#mod; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:42:13 + | +LL | / unsafe { +LL | | +LL | | $v; +LL | | } + | |_____________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:67:17 + | +LL | / unsafe { +LL | | +LL | | $v; +LL | | } + | |_________________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:95:21 + | +LL | unsafe { $inside_unsafe } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:110:17 + | +LL | / unsafe { +LL | | +LL | | $inside_unsafe; +LL | | } + | |_________________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:122:9 + | +LL | / unsafe { +LL | | +LL | | $inside_unsafe; +LL | | let inside_unsafe = 1; +LL | | inside_unsafe; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:137:9 + | +LL | / unsafe { +LL | | +LL | | $inside_unsafe; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:146:13 + | +LL | / unsafe { +LL | | +LL | | $x; +LL | | } + | |_____________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:171:5 + | +LL | / unsafe { +LL | | +LL | | $v; +LL | | } + | |_____^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:158:9 + | +LL | / unsafe { +LL | | +LL | | $x; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:162:9 + | +LL | / unsafe { +LL | | +LL | | $x; +LL | | $y; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:222:9 + | +LL | / unsafe { +LL | | +LL | | $v; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:232:9 + | +LL | / unsafe { +LL | | +LL | | nested_macro_helper!($v); +LL | | $v; +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + +error: aborting due to 14 previous errors + diff --git a/tests/ui-toml/macro_metavars_in_unsafe/private/clippy.toml b/tests/ui-toml/macro_metavars_in_unsafe/private/clippy.toml new file mode 100644 index 0000000000000..d4bbc2a1be894 --- /dev/null +++ b/tests/ui-toml/macro_metavars_in_unsafe/private/clippy.toml @@ -0,0 +1 @@ +warn-unsafe-macro-metavars-in-private-macros = true diff --git a/tests/ui-toml/macro_metavars_in_unsafe/private/test.rs b/tests/ui-toml/macro_metavars_in_unsafe/private/test.rs new file mode 100644 index 0000000000000..2bbe1fa7b7f94 --- /dev/null +++ b/tests/ui-toml/macro_metavars_in_unsafe/private/test.rs @@ -0,0 +1,15 @@ +//! Tests macro_metavars_in_unsafe with private (non-exported) macros +#![warn(clippy::macro_metavars_in_unsafe)] + +macro_rules! mac { + ($v:expr) => { + unsafe { + //~^ ERROR: this macro expands metavariables in an unsafe block + dbg!($v); + } + }; +} + +fn main() { + mac!(1); +} diff --git a/tests/ui-toml/macro_metavars_in_unsafe/private/test.stderr b/tests/ui-toml/macro_metavars_in_unsafe/private/test.stderr new file mode 100644 index 0000000000000..f9c418b221887 --- /dev/null +++ b/tests/ui-toml/macro_metavars_in_unsafe/private/test.stderr @@ -0,0 +1,17 @@ +error: this macro expands metavariables in an unsafe block + --> tests/ui-toml/macro_metavars_in_unsafe/private/test.rs:6:9 + | +LL | / unsafe { +LL | | +LL | | dbg!($v); +LL | | } + | |_________^ + | + = note: this allows the user of the macro to write unsafe code outside of an unsafe block + = help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable + = help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite + = note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/panic/clippy.toml b/tests/ui-toml/panic/clippy.toml new file mode 100644 index 0000000000000..5d6230d092c07 --- /dev/null +++ b/tests/ui-toml/panic/clippy.toml @@ -0,0 +1 @@ +allow-panic-in-tests = true diff --git a/tests/ui-toml/panic/panic.rs b/tests/ui-toml/panic/panic.rs new file mode 100644 index 0000000000000..618a37ddfc555 --- /dev/null +++ b/tests/ui-toml/panic/panic.rs @@ -0,0 +1,54 @@ +//@compile-flags: --test +#![warn(clippy::panic)] + +fn main() { + enum Enam { + A, + } + let a = Enam::A; + match a { + Enam::A => {}, + _ => panic!(""), + } +} + +#[test] +fn lonely_test() { + enum Enam { + A, + } + let a = Enam::A; + match a { + Enam::A => {}, + _ => panic!(""), + } +} + +#[cfg(test)] +mod tests { + // should not lint in `#[cfg(test)]` modules + #[test] + fn test_fn() { + enum Enam { + A, + } + let a = Enam::A; + match a { + Enam::A => {}, + _ => panic!(""), + } + + bar(); + } + + fn bar() { + enum Enam { + A, + } + let a = Enam::A; + match a { + Enam::A => {}, + _ => panic!(""), + } + } +} diff --git a/tests/ui-toml/panic/panic.stderr b/tests/ui-toml/panic/panic.stderr new file mode 100644 index 0000000000000..bf7503e086c9d --- /dev/null +++ b/tests/ui-toml/panic/panic.stderr @@ -0,0 +1,11 @@ +error: `panic` should not be present in production code + --> tests/ui-toml/panic/panic.rs:11:14 + | +LL | _ => panic!(""), + | ^^^^^^^^^^ + | + = note: `-D clippy::panic` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::panic)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/renamed_function_params/default/clippy.toml b/tests/ui-toml/renamed_function_params/default/clippy.toml new file mode 100644 index 0000000000000..5381e70a93915 --- /dev/null +++ b/tests/ui-toml/renamed_function_params/default/clippy.toml @@ -0,0 +1,2 @@ +# Ignore `From`, `TryFrom`, `FromStr` by default +# allow-renamed-params-for = [] diff --git a/tests/ui-toml/renamed_function_params/extend/clippy.toml b/tests/ui-toml/renamed_function_params/extend/clippy.toml new file mode 100644 index 0000000000000..9b3853e769615 --- /dev/null +++ b/tests/ui-toml/renamed_function_params/extend/clippy.toml @@ -0,0 +1,2 @@ +# Ignore `From`, `TryFrom`, `FromStr` by default +allow-renamed-params-for = [ "..", "std::ops::Add", "renamed_function_params::MyTrait" ] diff --git a/tests/ui-toml/renamed_function_params/renamed_function_params.default.stderr b/tests/ui-toml/renamed_function_params/renamed_function_params.default.stderr new file mode 100644 index 0000000000000..2d700f6075926 --- /dev/null +++ b/tests/ui-toml/renamed_function_params/renamed_function_params.default.stderr @@ -0,0 +1,46 @@ +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:30:18 + | +LL | fn eq(&self, rhs: &Self) -> bool { + | ^^^ help: consider using the default name: `other` + | + = note: `-D clippy::renamed-function-params` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::renamed_function_params)]` + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:34:18 + | +LL | fn ne(&self, rhs: &Self) -> bool { + | ^^^ help: consider using the default name: `other` + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:48:19 + | +LL | fn foo(&self, i_dont_wanna_use_your_name: u8) {} // only lint in `extend` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the default name: `val` + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:55:31 + | +LL | fn hash(&self, states: &mut H) { + | ^^^^^^ help: consider using the default name: `state` + +error: renamed function parameters of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:59:30 + | +LL | fn hash_slice(date: &[Self], states: &mut H) { + | ^^^^ ^^^^^^ + | +help: consider using the default names + | +LL | fn hash_slice(data: &[Self], state: &mut H) { + | ~~~~ ~~~~~ + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:80:18 + | +LL | fn add(self, b: B) -> C { + | ^ help: consider using the default name: `rhs` + +error: aborting due to 6 previous errors + diff --git a/tests/ui-toml/renamed_function_params/renamed_function_params.extend.stderr b/tests/ui-toml/renamed_function_params/renamed_function_params.extend.stderr new file mode 100644 index 0000000000000..e57554fa613ac --- /dev/null +++ b/tests/ui-toml/renamed_function_params/renamed_function_params.extend.stderr @@ -0,0 +1,34 @@ +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:30:18 + | +LL | fn eq(&self, rhs: &Self) -> bool { + | ^^^ help: consider using the default name: `other` + | + = note: `-D clippy::renamed-function-params` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::renamed_function_params)]` + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:34:18 + | +LL | fn ne(&self, rhs: &Self) -> bool { + | ^^^ help: consider using the default name: `other` + +error: renamed function parameter of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:55:31 + | +LL | fn hash(&self, states: &mut H) { + | ^^^^^^ help: consider using the default name: `state` + +error: renamed function parameters of trait impl + --> tests/ui-toml/renamed_function_params/renamed_function_params.rs:59:30 + | +LL | fn hash_slice(date: &[Self], states: &mut H) { + | ^^^^ ^^^^^^ + | +help: consider using the default names + | +LL | fn hash_slice(data: &[Self], state: &mut H) { + | ~~~~ ~~~~~ + +error: aborting due to 4 previous errors + diff --git a/tests/ui-toml/renamed_function_params/renamed_function_params.rs b/tests/ui-toml/renamed_function_params/renamed_function_params.rs new file mode 100644 index 0000000000000..f3eb910abbd67 --- /dev/null +++ b/tests/ui-toml/renamed_function_params/renamed_function_params.rs @@ -0,0 +1,110 @@ +//@no-rustfix +//@revisions: default extend +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/renamed_function_params/default +//@[extend] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/renamed_function_params/extend +#![warn(clippy::renamed_function_params)] +#![allow(clippy::partialeq_ne_impl, clippy::to_string_trait_impl)] +#![allow(unused)] + +use std::hash::{Hash, Hasher}; + +struct A; +impl From for String { + fn from(_value: A) -> Self { + String::new() + } +} +impl ToString for A { + fn to_string(&self) -> String { + String::new() + } +} + +struct B(u32); +impl std::convert::From for String { + fn from(b: B) -> Self { + b.0.to_string() + } +} +impl PartialEq for B { + fn eq(&self, rhs: &Self) -> bool { + //~^ ERROR: renamed function parameter of trait impl + self.0 == rhs.0 + } + fn ne(&self, rhs: &Self) -> bool { + //~^ ERROR: renamed function parameter of trait impl + self.0 != rhs.0 + } +} + +trait MyTrait { + fn foo(&self, val: u8); + fn bar(a: u8, b: u8); + fn baz(self, _val: u8); + fn quz(&self, _: u8); +} + +impl MyTrait for B { + fn foo(&self, i_dont_wanna_use_your_name: u8) {} // only lint in `extend` + fn bar(_a: u8, _: u8) {} + fn baz(self, val: u8) {} + fn quz(&self, val: u8) {} +} + +impl Hash for B { + fn hash(&self, states: &mut H) { + //~^ ERROR: renamed function parameter of trait impl + self.0.hash(states); + } + fn hash_slice(date: &[Self], states: &mut H) { + //~^ ERROR: renamed function parameters of trait impl + for d in date { + d.hash(states); + } + } +} + +impl B { + fn totally_irrelevant(&self, right: bool) {} + fn some_fn(&self, other: impl MyTrait) {} +} + +#[derive(Copy, Clone)] +enum C { + A, + B(u32), +} + +impl std::ops::Add for C { + type Output = C; + fn add(self, b: B) -> C { + // only lint in `extend` + C::B(b.0) + } +} + +impl From for C { + fn from(_: A) -> C { + C::A + } +} + +trait CustomTraitA { + fn foo(&self, other: u32); +} +trait CustomTraitB { + fn bar(&self, value: u8); +} + +macro_rules! impl_trait { + ($impl_for:ident, $tr:ty, $fn_name:ident, $t:ty) => { + impl $tr for $impl_for { + fn $fn_name(&self, v: $t) {} + } + }; +} + +impl_trait!(C, CustomTraitA, foo, u32); +impl_trait!(C, CustomTraitB, bar, u8); + +fn main() {} diff --git a/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs b/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs index 7f28efd676f2c..f02bd07cfe7bf 100644 --- a/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs +++ b/tests/ui-toml/toml_disallowed_types/conf_disallowed_types.rs @@ -40,3 +40,9 @@ fn main() { let _ = HashMap; let _: usize = 64_usize; } + +mod useless_attribute { + // Regression test for https://github.com/rust-lang/rust-clippy/issues/12753 + #[allow(clippy::disallowed_types)] + use std::collections::HashMap; +} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 722e9b3bc8d46..5cf9c0fb27102 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -8,8 +8,10 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect allow-expect-in-tests allow-mixed-uninlined-format-args allow-one-hash-in-raw-strings + allow-panic-in-tests allow-print-in-tests allow-private-module-inception + allow-renamed-params-for allow-unwrap-in-tests allow-useless-vec-in-tests allowed-dotfiles @@ -74,6 +76,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect vec-box-size-threshold verbose-bit-mask-threshold warn-on-all-wildcard-imports + warn-unsafe-macro-metavars-in-private-macros --> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:2:1 | LL | foobar = 42 @@ -89,8 +92,10 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect allow-expect-in-tests allow-mixed-uninlined-format-args allow-one-hash-in-raw-strings + allow-panic-in-tests allow-print-in-tests allow-private-module-inception + allow-renamed-params-for allow-unwrap-in-tests allow-useless-vec-in-tests allowed-dotfiles @@ -155,6 +160,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect vec-box-size-threshold verbose-bit-mask-threshold warn-on-all-wildcard-imports + warn-unsafe-macro-metavars-in-private-macros --> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:4:1 | LL | barfoo = 53 @@ -170,8 +176,10 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni allow-expect-in-tests allow-mixed-uninlined-format-args allow-one-hash-in-raw-strings + allow-panic-in-tests allow-print-in-tests allow-private-module-inception + allow-renamed-params-for allow-unwrap-in-tests allow-useless-vec-in-tests allowed-dotfiles @@ -236,6 +244,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni vec-box-size-threshold verbose-bit-mask-threshold warn-on-all-wildcard-imports + warn-unsafe-macro-metavars-in-private-macros --> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:7:1 | LL | allow_mixed_uninlined_format_args = true diff --git a/tests/ui/assigning_clones.fixed b/tests/ui/assigning_clones.fixed index 8387c7d6156b5..70ab43b49b3a7 100644 --- a/tests/ui/assigning_clones.fixed +++ b/tests/ui/assigning_clones.fixed @@ -62,6 +62,16 @@ fn clone_method_rhs_complex(mut_thing: &mut HasCloneFrom, ref_thing: &HasCloneFr mut_thing.clone_from(ref_thing + ref_thing); } +fn clone_method_macro() { + let mut s = String::from(""); + s.clone_from(&format!("{} {}", "hello", "world")); +} + +fn clone_function_macro() { + let mut s = String::from(""); + Clone::clone_from(&mut s, &format!("{} {}", "hello", "world")); +} + fn assign_to_init_mut_var(b: HasCloneFrom) -> HasCloneFrom { let mut a = HasCloneFrom; for _ in 1..10 { @@ -86,6 +96,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) { a = b.clone(); } +fn late_init_let_tuple() { + let (p, q): (String, String); + p = "ghi".to_string(); + q = p.clone(); +} + #[derive(Clone)] pub struct HasDeriveClone; @@ -208,6 +224,16 @@ fn owned_function_val(mut mut_thing: String, ref_str: &str) { ToOwned::clone_into(ref_str, &mut mut_thing); } +fn owned_method_macro() { + let mut s = String::from(""); + format!("{} {}", "hello", "world").clone_into(&mut s); +} + +fn owned_function_macro() { + let mut s = String::from(""); + ToOwned::clone_into(&format!("{} {}", "hello", "world"), &mut s); +} + struct FakeToOwned; impl FakeToOwned { /// This looks just like `ToOwned::to_owned` diff --git a/tests/ui/assigning_clones.rs b/tests/ui/assigning_clones.rs index 6f4da9f652c99..9699fed100c8f 100644 --- a/tests/ui/assigning_clones.rs +++ b/tests/ui/assigning_clones.rs @@ -62,6 +62,16 @@ fn clone_method_rhs_complex(mut_thing: &mut HasCloneFrom, ref_thing: &HasCloneFr *mut_thing = (ref_thing + ref_thing).clone(); } +fn clone_method_macro() { + let mut s = String::from(""); + s = format!("{} {}", "hello", "world").clone(); +} + +fn clone_function_macro() { + let mut s = String::from(""); + s = Clone::clone(&format!("{} {}", "hello", "world")); +} + fn assign_to_init_mut_var(b: HasCloneFrom) -> HasCloneFrom { let mut a = HasCloneFrom; for _ in 1..10 { @@ -86,6 +96,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) { a = b.clone(); } +fn late_init_let_tuple() { + let (p, q): (String, String); + p = "ghi".to_string(); + q = p.clone(); +} + #[derive(Clone)] pub struct HasDeriveClone; @@ -208,6 +224,16 @@ fn owned_function_val(mut mut_thing: String, ref_str: &str) { mut_thing = ToOwned::to_owned(ref_str); } +fn owned_method_macro() { + let mut s = String::from(""); + s = format!("{} {}", "hello", "world").to_owned(); +} + +fn owned_function_macro() { + let mut s = String::from(""); + s = ToOwned::to_owned(&format!("{} {}", "hello", "world")); +} + struct FakeToOwned; impl FakeToOwned { /// This looks just like `ToOwned::to_owned` diff --git a/tests/ui/assigning_clones.stderr b/tests/ui/assigning_clones.stderr index 793927bd1cb10..a68516376abb5 100644 --- a/tests/ui/assigning_clones.stderr +++ b/tests/ui/assigning_clones.stderr @@ -62,64 +62,88 @@ LL | *mut_thing = (ref_thing + ref_thing).clone(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `mut_thing.clone_from(ref_thing + ref_thing)` error: assigning the result of `Clone::clone()` may be inefficient - --> tests/ui/assigning_clones.rs:68:9 + --> tests/ui/assigning_clones.rs:67:5 + | +LL | s = format!("{} {}", "hello", "world").clone(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `s.clone_from(&format!("{} {}", "hello", "world"))` + +error: assigning the result of `Clone::clone()` may be inefficient + --> tests/ui/assigning_clones.rs:72:5 + | +LL | s = Clone::clone(&format!("{} {}", "hello", "world")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `Clone::clone_from(&mut s, &format!("{} {}", "hello", "world"))` + +error: assigning the result of `Clone::clone()` may be inefficient + --> tests/ui/assigning_clones.rs:78:9 | LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `Clone::clone()` may be inefficient - --> tests/ui/assigning_clones.rs:133:5 + --> tests/ui/assigning_clones.rs:149:5 | LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `Clone::clone()` may be inefficient - --> tests/ui/assigning_clones.rs:140:5 + --> tests/ui/assigning_clones.rs:156:5 | LL | a = b.clone(); | ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:141:5 + --> tests/ui/assigning_clones.rs:157:5 | LL | a = c.to_owned(); | ^^^^^^^^^^^^^^^^ help: use `clone_into()`: `c.clone_into(&mut a)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:171:5 + --> tests/ui/assigning_clones.rs:187:5 | LL | *mut_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(mut_string)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:175:5 + --> tests/ui/assigning_clones.rs:191:5 | LL | mut_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut mut_string)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:196:5 + --> tests/ui/assigning_clones.rs:212:5 | LL | **mut_box_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:200:5 + --> tests/ui/assigning_clones.rs:216:5 | LL | **mut_box_string = ref_str.to_owned(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:204:5 + --> tests/ui/assigning_clones.rs:220:5 | LL | *mut_thing = ToOwned::to_owned(ref_str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, mut_thing)` error: assigning the result of `ToOwned::to_owned()` may be inefficient - --> tests/ui/assigning_clones.rs:208:5 + --> tests/ui/assigning_clones.rs:224:5 | LL | mut_thing = ToOwned::to_owned(ref_str); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, &mut mut_thing)` -error: aborting due to 20 previous errors +error: assigning the result of `ToOwned::to_owned()` may be inefficient + --> tests/ui/assigning_clones.rs:229:5 + | +LL | s = format!("{} {}", "hello", "world").to_owned(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `format!("{} {}", "hello", "world").clone_into(&mut s)` + +error: assigning the result of `ToOwned::to_owned()` may be inefficient + --> tests/ui/assigning_clones.rs:234:5 + | +LL | s = ToOwned::to_owned(&format!("{} {}", "hello", "world")); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(&format!("{} {}", "hello", "world"), &mut s)` + +error: aborting due to 24 previous errors diff --git a/tests/ui/doc/doc_lazy_blockquote.fixed b/tests/ui/doc/doc_lazy_blockquote.fixed new file mode 100644 index 0000000000000..9877991f183a5 --- /dev/null +++ b/tests/ui/doc/doc_lazy_blockquote.fixed @@ -0,0 +1,47 @@ +#![warn(clippy::doc_lazy_continuation)] + +/// > blockquote with +/// > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn first() {} + +/// > blockquote with no +/// > lazy continuation +fn first_nowarn() {} + +/// > blockquote with no +/// +/// lazy continuation +fn two_nowarn() {} + +/// > nest here +/// > +/// > > nest here +/// > > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn two() {} + +/// > nest here +/// > +/// > > nest here +/// > > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn three() {} + +/// > * > nest here +/// > > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn four() {} + +/// > * > nest here +/// > > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn four_point_1() {} + +/// * > nest here lazy continuation +fn five() {} + +/// 1. > nest here +/// > lazy continuation (this results in strange indentation, but still works) +//~^ ERROR: doc quote missing `>` marker +fn six() {} diff --git a/tests/ui/doc/doc_lazy_blockquote.rs b/tests/ui/doc/doc_lazy_blockquote.rs new file mode 100644 index 0000000000000..587b2fdd533ce --- /dev/null +++ b/tests/ui/doc/doc_lazy_blockquote.rs @@ -0,0 +1,47 @@ +#![warn(clippy::doc_lazy_continuation)] + +/// > blockquote with +/// lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn first() {} + +/// > blockquote with no +/// > lazy continuation +fn first_nowarn() {} + +/// > blockquote with no +/// +/// lazy continuation +fn two_nowarn() {} + +/// > nest here +/// > +/// > > nest here +/// > lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn two() {} + +/// > nest here +/// > +/// > > nest here +/// lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn three() {} + +/// > * > nest here +/// lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn four() {} + +/// > * > nest here +/// lazy continuation +//~^ ERROR: doc quote missing `>` marker +fn four_point_1() {} + +/// * > nest here lazy continuation +fn five() {} + +/// 1. > nest here +/// lazy continuation (this results in strange indentation, but still works) +//~^ ERROR: doc quote missing `>` marker +fn six() {} diff --git a/tests/ui/doc/doc_lazy_blockquote.stderr b/tests/ui/doc/doc_lazy_blockquote.stderr new file mode 100644 index 0000000000000..975184a01c3f0 --- /dev/null +++ b/tests/ui/doc/doc_lazy_blockquote.stderr @@ -0,0 +1,76 @@ +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:4:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` + = note: `-D clippy::doc-lazy-continuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]` +help: add markers to start of line + | +LL | /// > lazy continuation + | + + +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:20:5 + | +LL | /// > lazy continuation + | ^^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: add markers to start of line + | +LL | /// > > lazy continuation + | + + +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:27:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: add markers to start of line + | +LL | /// > > lazy continuation + | +++ + +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:32:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: add markers to start of line + | +LL | /// > > lazy continuation + | +++++++ + +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:37:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: add markers to start of line + | +LL | /// > > lazy continuation + | +++++ + +error: doc quote missing `>` marker + --> tests/ui/doc/doc_lazy_blockquote.rs:45:5 + | +LL | /// lazy continuation (this results in strange indentation, but still works) + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: add markers to start of line + | +LL | /// > lazy continuation (this results in strange indentation, but still works) + | + + +error: aborting due to 6 previous errors + diff --git a/tests/ui/doc/doc_lazy_list.fixed b/tests/ui/doc/doc_lazy_list.fixed new file mode 100644 index 0000000000000..409e6b0bc227f --- /dev/null +++ b/tests/ui/doc/doc_lazy_list.fixed @@ -0,0 +1,77 @@ +#![warn(clippy::doc_lazy_continuation)] + +/// 1. nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn one() {} + +/// 1. first line +/// lazy list continuations don't make warnings with this lint +//~^ ERROR: doc list item missing indentation +/// because they don't have the +//~^ ERROR: doc list item missing indentation +fn two() {} + +/// - nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn three() {} + +/// - first line +/// lazy list continuations don't make warnings with this lint +//~^ ERROR: doc list item missing indentation +/// because they don't have the +//~^ ERROR: doc list item missing indentation +fn four() {} + +/// - nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn five() {} + +/// - - first line +/// this will warn on the lazy continuation +//~^ ERROR: doc list item missing indentation +/// and so should this +//~^ ERROR: doc list item missing indentation +fn six() {} + +/// - - first line +/// +/// this is not a lazy continuation +fn seven() {} + +#[rustfmt::skip] +// https://github.com/rust-lang/rust-clippy/pull/12770#issuecomment-2118601768 +/// Returns a list of ProtocolDescriptors from a Serde JSON input. +/// +/// Defined Protocol Identifiers for the Protocol Descriptor +/// We intentionally omit deprecated profile identifiers. +/// From Bluetooth Assigned Numbers: +/// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery +/// +/// # Arguments +/// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors +/// to set up. Example: +/// 'protocol_descriptors': [ +//~^ ERROR: doc list item missing indentation +/// { +/// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP +/// 'params': [ +/// { +/// 'data': 0x0103 # to indicate 1.3 +/// }, +/// { +/// 'data': 0x0105 # to indicate 1.5 +/// } +/// ] +/// }, +/// { +/// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP +/// 'params': [{ +/// 'data': 0x0019 +/// }] +/// } +/// ] +//~^ ERROR: doc list item missing indentation +fn eight() {} diff --git a/tests/ui/doc/doc_lazy_list.rs b/tests/ui/doc/doc_lazy_list.rs new file mode 100644 index 0000000000000..30ab448a1130c --- /dev/null +++ b/tests/ui/doc/doc_lazy_list.rs @@ -0,0 +1,77 @@ +#![warn(clippy::doc_lazy_continuation)] + +/// 1. nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn one() {} + +/// 1. first line +/// lazy list continuations don't make warnings with this lint +//~^ ERROR: doc list item missing indentation +/// because they don't have the +//~^ ERROR: doc list item missing indentation +fn two() {} + +/// - nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn three() {} + +/// - first line +/// lazy list continuations don't make warnings with this lint +//~^ ERROR: doc list item missing indentation +/// because they don't have the +//~^ ERROR: doc list item missing indentation +fn four() {} + +/// - nest here +/// lazy continuation +//~^ ERROR: doc list item missing indentation +fn five() {} + +/// - - first line +/// this will warn on the lazy continuation +//~^ ERROR: doc list item missing indentation +/// and so should this +//~^ ERROR: doc list item missing indentation +fn six() {} + +/// - - first line +/// +/// this is not a lazy continuation +fn seven() {} + +#[rustfmt::skip] +// https://github.com/rust-lang/rust-clippy/pull/12770#issuecomment-2118601768 +/// Returns a list of ProtocolDescriptors from a Serde JSON input. +/// +/// Defined Protocol Identifiers for the Protocol Descriptor +/// We intentionally omit deprecated profile identifiers. +/// From Bluetooth Assigned Numbers: +/// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery +/// +/// # Arguments +/// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors +/// to set up. Example: +/// 'protocol_descriptors': [ +//~^ ERROR: doc list item missing indentation +/// { +/// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP +/// 'params': [ +/// { +/// 'data': 0x0103 # to indicate 1.3 +/// }, +/// { +/// 'data': 0x0105 # to indicate 1.5 +/// } +/// ] +/// }, +/// { +/// 'protocol': 1, # u64 Representation of ProtocolIdentifier::SDP +/// 'params': [{ +/// 'data': 0x0019 +/// }] +/// } +/// ] +//~^ ERROR: doc list item missing indentation +fn eight() {} diff --git a/tests/ui/doc/doc_lazy_list.stderr b/tests/ui/doc/doc_lazy_list.stderr new file mode 100644 index 0000000000000..ddfdc49340c45 --- /dev/null +++ b/tests/ui/doc/doc_lazy_list.stderr @@ -0,0 +1,136 @@ +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:4:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line + = note: `-D clippy::doc-lazy-continuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]` +help: indent this line + | +LL | /// lazy continuation + | +++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:9:5 + | +LL | /// lazy list continuations don't make warnings with this lint + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// lazy list continuations don't make warnings with this lint + | +++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:11:5 + | +LL | /// because they don't have the + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// because they don't have the + | +++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:16:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// lazy continuation + | ++++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:21:5 + | +LL | /// lazy list continuations don't make warnings with this lint + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// lazy list continuations don't make warnings with this lint + | ++++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:23:5 + | +LL | /// because they don't have the + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// because they don't have the + | ++++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:28:5 + | +LL | /// lazy continuation + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// lazy continuation + | ++++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:33:5 + | +LL | /// this will warn on the lazy continuation + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// this will warn on the lazy continuation + | ++++++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:35:5 + | +LL | /// and so should this + | ^^^^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// and so should this + | ++ + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:56:5 + | +LL | /// 'protocol_descriptors': [ + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// 'protocol_descriptors': [ + | + + +error: doc list item missing indentation + --> tests/ui/doc/doc_lazy_list.rs:75:5 + | +LL | /// ] + | ^ + | + = help: if this is supposed to be its own paragraph, add a blank line +help: indent this line + | +LL | /// ] + | + + +error: aborting due to 11 previous errors + diff --git a/tests/ui/duplicated_attributes.rs b/tests/ui/duplicated_attributes.rs index d51e7e37beb67..97cf4a69682d7 100644 --- a/tests/ui/duplicated_attributes.rs +++ b/tests/ui/duplicated_attributes.rs @@ -1,5 +1,5 @@ //@aux-build:proc_macro_attr.rs - +#![feature(rustc_attrs)] #![warn(clippy::duplicated_attributes)] #![cfg(any(unix, windows))] #![allow(dead_code)] @@ -20,6 +20,10 @@ fn foo() {} #[cfg(unix)] // cfgs are not handled fn bar() {} +// No warning: +#[rustc_on_unimplemented(on(_Self = "&str", label = "`a"), on(_Self = "alloc::string::String", label = "a"))] +trait Abc {} + #[proc_macro_attr::duplicated_attr()] // Should not warn! fn babar() {} diff --git a/tests/ui/from_str_radix_10.fixed b/tests/ui/from_str_radix_10.fixed index 8c253bfd99a57..f9ce1defda17c 100644 --- a/tests/ui/from_str_radix_10.fixed +++ b/tests/ui/from_str_radix_10.fixed @@ -1,3 +1,4 @@ +#![feature(const_int_from_str)] #![warn(clippy::from_str_radix_10)] mod some_mod { @@ -59,3 +60,13 @@ fn main() -> Result<(), Box> { Ok(()) } + +fn issue_12732() { + const A: Result = u32::from_str_radix("123", 10); + const B: () = { + let _ = u32::from_str_radix("123", 10); + }; + const fn foo() { + let _ = u32::from_str_radix("123", 10); + } +} diff --git a/tests/ui/from_str_radix_10.rs b/tests/ui/from_str_radix_10.rs index e9d02215710cb..2d5b351f8da3e 100644 --- a/tests/ui/from_str_radix_10.rs +++ b/tests/ui/from_str_radix_10.rs @@ -1,3 +1,4 @@ +#![feature(const_int_from_str)] #![warn(clippy::from_str_radix_10)] mod some_mod { @@ -59,3 +60,13 @@ fn main() -> Result<(), Box> { Ok(()) } + +fn issue_12732() { + const A: Result = u32::from_str_radix("123", 10); + const B: () = { + let _ = u32::from_str_radix("123", 10); + }; + const fn foo() { + let _ = u32::from_str_radix("123", 10); + } +} diff --git a/tests/ui/from_str_radix_10.stderr b/tests/ui/from_str_radix_10.stderr index 4aa84eca26120..01a1bf8940a12 100644 --- a/tests/ui/from_str_radix_10.stderr +++ b/tests/ui/from_str_radix_10.stderr @@ -1,5 +1,5 @@ error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:28:5 + --> tests/ui/from_str_radix_10.rs:29:5 | LL | u32::from_str_radix("30", 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::()` @@ -8,43 +8,43 @@ LL | u32::from_str_radix("30", 10)?; = help: to override `-D warnings` add `#[allow(clippy::from_str_radix_10)]` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:31:5 + --> tests/ui/from_str_radix_10.rs:32:5 | LL | i64::from_str_radix("24", 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:33:5 + --> tests/ui/from_str_radix_10.rs:34:5 | LL | isize::from_str_radix("100", 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:35:5 + --> tests/ui/from_str_radix_10.rs:36:5 | LL | u8::from_str_radix("7", 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:37:5 + --> tests/ui/from_str_radix_10.rs:38:5 | LL | u16::from_str_radix(&("10".to_owned() + "5"), 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `("10".to_owned() + "5").parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:39:5 + --> tests/ui/from_str_radix_10.rs:40:5 | LL | i128::from_str_radix(Test + Test, 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:43:5 + --> tests/ui/from_str_radix_10.rs:44:5 | LL | i32::from_str_radix(string, 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::()` error: this call to `from_str_radix` can be replaced with a call to `str::parse` - --> tests/ui/from_str_radix_10.rs:47:5 + --> tests/ui/from_str_radix_10.rs:48:5 | LL | i32::from_str_radix(&stringier, 10)?; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::()` diff --git a/tests/ui/match_same_arms.stderr b/tests/ui/match_same_arms.stderr index a926570b60ada..3c0382767c3fc 100644 --- a/tests/ui/match_same_arms.stderr +++ b/tests/ui/match_same_arms.stderr @@ -2,7 +2,7 @@ error: this match arm has an identical body to the `_` wildcard arm --> tests/ui/match_same_arms.rs:12:9 | LL | Abc::A => 0, - | ^^^^^^^^^^^ help: try removing the arm + | ^^^^^^^^^^^^^ help: try removing the arm | = help: or try changing either arm body note: `_` wildcard arm here @@ -17,106 +17,114 @@ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:18:9 | LL | (1, .., 3) => 42, - | ----------^^^^^^ - | | - | help: try merging the arm patterns: `(1, .., 3) | (.., 3)` + | ^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:19:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | (1, .., 3) | (.., 3) => 42, + | ~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - (.., 3) => 42, | -LL | (.., 3) => 42, - | ^^^^^^^^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:25:9 | LL | 51 => 1, - | --^^^^^ - | | - | help: try merging the arm patterns: `51 | 42` + | ^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:24:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 51 | 42 => 1, + | ~~~~~~~ +help: and remove this obsolete arm + | +LL - 42 => 1, | -LL | 42 => 1, - | ^^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:26:9 | LL | 41 => 2, - | --^^^^^ - | | - | help: try merging the arm patterns: `41 | 52` + | ^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:27:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 41 | 52 => 2, + | ~~~~~~~ +help: and remove this obsolete arm + | +LL - 52 => 2, | -LL | 52 => 2, - | ^^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:33:9 | LL | 2 => 2, - | -^^^^^ - | | - | help: try merging the arm patterns: `2 | 1` + | ^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:32:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 2 | 1 => 2, + | ~~~~~ +help: and remove this obsolete arm + | +LL - 1 => 2, | -LL | 1 => 2, - | ^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:35:9 | LL | 3 => 2, - | -^^^^^ - | | - | help: try merging the arm patterns: `3 | 1` + | ^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:32:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 3 | 1 => 2, + | ~~~~~ +help: and remove this obsolete arm + | +LL - 1 => 2, | -LL | 1 => 2, - | ^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:33:9 | LL | 2 => 2, - | -^^^^^ - | | - | help: try merging the arm patterns: `2 | 3` + | ^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:35:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 2 | 3 => 2, + | ~~~~~ +help: and remove this obsolete arm + | +LL - 3 => 2, +LL + | -LL | 3 => 2, - | ^^^^^^ error: this match arm has an identical body to another arm --> tests/ui/match_same_arms.rs:52:17 | LL | CommandInfo::External { name, .. } => name.to_string(), - | ----------------------------------^^^^^^^^^^^^^^^^^^^^ - | | - | help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms.rs:51:17 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. } => name.to_string(), + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - CommandInfo::BuiltIn { name, .. } => name.to_string(), | -LL | CommandInfo::BuiltIn { name, .. } => name.to_string(), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: aborting due to 8 previous errors diff --git a/tests/ui/match_same_arms2.fixed b/tests/ui/match_same_arms2.fixed new file mode 100644 index 0000000000000..fba0cf33b3c26 --- /dev/null +++ b/tests/ui/match_same_arms2.fixed @@ -0,0 +1,241 @@ +#![warn(clippy::match_same_arms)] +#![allow( + clippy::disallowed_names, + clippy::diverging_sub_expression, + clippy::uninlined_format_args, + clippy::match_single_binding, + clippy::match_like_matches_macro +)] +fn bar(_: T) {} +fn foo() -> bool { + unimplemented!() +} + +fn match_same_arms() { + let _ = match 42 { + _ => { + foo(); + let mut a = 42 + [23].len() as i32; + if true { + a += 7; + } + a = -31 - a; + a + }, + }; + //~^^^^^^^^^^^^^^^^^^^ ERROR: this match arm has an identical body to the `_` wildcard arm + + let _ = match 42 { + 51 | 42 => foo(), //~ ERROR: this match arm has an identical body to another arm + _ => true, + }; + + let _ = match Some(42) { + None | Some(_) => 24, //~ ERROR: this match arm has an identical body to another arm + }; + + let _ = match Some(42) { + Some(foo) => 24, + None => 24, + }; + + let _ = match Some(42) { + Some(42) => 24, + Some(a) => 24, // bindings are different + None => 0, + }; + + let _ = match Some(42) { + Some(a) if a > 0 => 24, + Some(a) => 24, // one arm has a guard + None => 0, + }; + + match (Some(42), Some(42)) { + (None, Some(a)) | (Some(a), None) => bar(a), //~ ERROR: this match arm has an identical body to another arm + _ => (), + } + + // No warning because guards are different + let _ = match Some(42) { + Some(a) if a == 42 => a, + Some(a) if a == 24 => a, + Some(_) => 24, + None => 0, + }; + + let _ = match (Some(42), Some(42)) { + (None, Some(a)) | (Some(a), None) if a == 42 => a, //~ ERROR: this match arm has an identical body to another arm + _ => 0, + }; + + match (Some(42), Some(42)) { + (Some(a), ..) | (.., Some(a)) => bar(a), //~ ERROR: this match arm has an identical body to another arm + _ => (), + } + + let _ = match Some(()) { + Some(()) => 0.0, + None => -0.0, + }; + + match (Some(42), Some("")) { + (Some(a), None) => bar(a), + (None, Some(a)) => bar(a), // bindings have different types + _ => (), + } + + let x: Result = Ok(3); + + // No warning because of the guard. + match x { + Ok(x) if x * x == 64 => println!("ok"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + // This used to be a false positive; see issue #1996. + match x { + Ok(3) => println!("ok"), + Ok(x) if x * x == 64 => println!("ok 64"), + Ok(_) => println!("ok"), + Err(_) => println!("err"), + } + + match (x, Some(1i32)) { + (Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x), //~ ERROR: this match arm has an identical body to another arm + _ => println!("err"), + } + + // No warning; different types for `x`. + match (x, Some(1.0f64)) { + (Ok(x), Some(_)) => println!("ok {}", x), + (Ok(_), Some(x)) => println!("ok {}", x), + _ => println!("err"), + } + + // False negative #2251. + match x { + Ok(_tmp) => println!("ok"), + Ok(_) | Ok(3) => println!("ok"), //~ ERROR: this match arm has an identical body to another arm + Err(_) => { + unreachable!(); + }, + } + + // False positive #1390 + macro_rules! empty { + ($e:expr) => {}; + } + match 0 { + 0 => { + empty!(0); + }, + 1 => { + empty!(1); + }, + x => { + empty!(x); + }, + }; + + // still lint if the tokens are the same + match 0 { + 1 | 0 => { + empty!(0); + }, + x => { + empty!(x); + }, + } + //~^^^^^^^ ERROR: this match arm has an identical body to another arm + + match_expr_like_matches_macro_priority(); +} + +fn match_expr_like_matches_macro_priority() { + enum E { + A, + B, + C, + } + let x = E::A; + let _ans = match x { + E::A => false, + E::B => false, + _ => true, + }; +} + +fn main() { + let _ = match Some(0) { + Some(0) => 0, + Some(1) => 1, + #[cfg(feature = "foo")] + Some(2) => 2, + _ => 1, + }; + + enum Foo { + X(u32), + Y(u32), + Z(u32), + } + + // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between. + let _ = match Foo::X(0) { + Foo::X(0) => 1, + Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) => 1, + _ => 0, + }; + + // Suggest moving `Foo::Z(_)` up. + let _ = match Foo::X(0) { + Foo::X(0) | Foo::Z(_) => 1, //~ ERROR: this match arm has an identical body to another arm + Foo::X(_) | Foo::Y(_) => 2, + _ => 0, + }; + + // Suggest moving `Foo::X(0)` down. + let _ = match Foo::X(0) { + Foo::Y(_) | Foo::Z(0) => 2, + Foo::Z(_) | Foo::X(0) => 1, //~ ERROR: this match arm has an identical body to another arm + _ => 0, + }; + + // Don't lint. + let _ = match 0 { + -2 => 1, + -5..=50 => 2, + -150..=88 => 1, + _ => 3, + }; + + struct Bar { + x: u32, + y: u32, + z: u32, + } + + // Lint. + let _ = match None { + Some(Bar { y: 10, z: 0, .. }) => 2, + None => 50, + Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1, //~ ERROR: this match arm has an identical body to another arm + _ => 200, + }; + + let _ = match 0 { + 0 => todo!(), + 1 => todo!(), + 2 => core::convert::identity::(todo!()), + 3 => core::convert::identity::(todo!()), + _ => 5, + }; + + let _ = match 0 { + 1 | 0 => cfg!(not_enable), + _ => false, + }; +} diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index 85ad0962eb4bf..8a4e3b325bbff 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -2,9 +2,10 @@ #![allow( clippy::disallowed_names, clippy::diverging_sub_expression, - clippy::uninlined_format_args + clippy::uninlined_format_args, + clippy::match_single_binding, + clippy::match_like_matches_macro )] -//@no-rustfix fn bar(_: T) {} fn foo() -> bool { unimplemented!() diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index f4c38c1af8975..3d15176ccf997 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -1,18 +1,18 @@ error: this match arm has an identical body to the `_` wildcard arm - --> tests/ui/match_same_arms2.rs:15:9 + --> tests/ui/match_same_arms2.rs:16:9 | LL | / 42 => { LL | | foo(); LL | | let mut a = 42 + [23].len() as i32; LL | | if true { ... | -LL | | a LL | | }, - | |_________^ help: try removing the arm +LL | | _ => { + | |________^ help: try removing the arm | = help: or try changing either arm body note: `_` wildcard arm here - --> tests/ui/match_same_arms2.rs:24:9 + --> tests/ui/match_same_arms2.rs:25:9 | LL | / _ => { LL | | foo(); @@ -26,203 +26,200 @@ LL | | }, = help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]` error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:38:9 + --> tests/ui/match_same_arms2.rs:39:9 | LL | 51 => foo(), - | --^^^^^^^^^ - | | - | help: try merging the arm patterns: `51 | 42` + | ^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:37:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 51 | 42 => foo(), + | ~~~~~~~ +help: and remove this obsolete arm + | +LL - 42 => foo(), | -LL | 42 => foo(), - | ^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:44:9 + --> tests/ui/match_same_arms2.rs:45:9 | LL | None => 24, - | ----^^^^^^ - | | - | help: try merging the arm patterns: `None | Some(_)` + | ^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:43:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | None | Some(_) => 24, + | ~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - Some(_) => 24, | -LL | Some(_) => 24, - | ^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:66:9 + --> tests/ui/match_same_arms2.rs:67:9 | LL | (None, Some(a)) => bar(a), - | ---------------^^^^^^^^^^ - | | - | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:65:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | (None, Some(a)) | (Some(a), None) => bar(a), + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - (Some(a), None) => bar(a), | -LL | (Some(a), None) => bar(a), - | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:80:9 + --> tests/ui/match_same_arms2.rs:81:9 | LL | (None, Some(a)) if a == 42 => a, - | ---------------^^^^^^^^^^^^^^^^ - | | - | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:79:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | (None, Some(a)) | (Some(a), None) if a == 42 => a, + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - (Some(a), None) if a == 42 => a, | -LL | (Some(a), None) if a == 42 => a, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:85:9 + --> tests/ui/match_same_arms2.rs:86:9 | LL | (Some(a), ..) => bar(a), - | -------------^^^^^^^^^^ - | | - | help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))` + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:86:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | (Some(a), ..) | (.., Some(a)) => bar(a), + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - (.., Some(a)) => bar(a), | -LL | (.., Some(a)) => bar(a), - | ^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:119:9 + --> tests/ui/match_same_arms2.rs:120:9 | LL | (Ok(x), Some(_)) => println!("ok {}", x), - | ----------------^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:120:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | (Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x), + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - (Ok(_), Some(x)) => println!("ok {}", x), | -LL | (Ok(_), Some(x)) => println!("ok {}", x), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:135:9 + --> tests/ui/match_same_arms2.rs:136:9 | LL | Ok(_) => println!("ok"), - | -----^^^^^^^^^^^^^^^^^^ - | | - | help: try merging the arm patterns: `Ok(_) | Ok(3)` + | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:134:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | Ok(_) | Ok(3) => println!("ok"), + | ~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - Ok(3) => println!("ok"), | -LL | Ok(3) => println!("ok"), - | ^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:162:9 + --> tests/ui/match_same_arms2.rs:163:9 | -LL | 1 => { - | ^ help: try merging the arm patterns: `1 | 0` - | _________| - | | +LL | / 1 => { LL | | empty!(0); LL | | }, | |_________^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:159:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 1 | 0 => { + | ~~~~~ +help: and remove this obsolete arm + | +LL - 0 => { +LL - empty!(0); +LL - }, | -LL | / 0 => { -LL | | empty!(0); -LL | | }, - | |_________^ - -error: match expression looks like `matches!` macro - --> tests/ui/match_same_arms2.rs:181:16 - | -LL | let _ans = match x { - | ________________^ -LL | | E::A => false, -LL | | E::B => false, -LL | | _ => true, -LL | | }; - | |_____^ help: try: `!matches!(x, E::A | E::B)` - | - = note: `-D clippy::match-like-matches-macro` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]` error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:213:9 + --> tests/ui/match_same_arms2.rs:214:9 | LL | Foo::X(0) => 1, - | ---------^^^^^ - | | - | help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)` + | ^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:215:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | Foo::X(0) | Foo::Z(_) => 1, + | ~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - Foo::Z(_) => 1, | -LL | Foo::Z(_) => 1, - | ^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:223:9 + --> tests/ui/match_same_arms2.rs:224:9 | LL | Foo::Z(_) => 1, - | ---------^^^^^ - | | - | help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)` + | ^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:221:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | Foo::Z(_) | Foo::X(0) => 1, + | ~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - Foo::X(0) => 1, | -LL | Foo::X(0) => 1, - | ^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:246:9 + --> tests/ui/match_same_arms2.rs:247:9 | LL | Some(Bar { y: 0, x: 5, .. }) => 1, - | ----------------------------^^^^^ - | | - | help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:243:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1, + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +help: and remove this obsolete arm + | +LL - Some(Bar { x: 0, y: 5, .. }) => 1, | -LL | Some(Bar { x: 0, y: 5, .. }) => 1, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this match arm has an identical body to another arm - --> tests/ui/match_same_arms2.rs:260:9 + --> tests/ui/match_same_arms2.rs:261:9 | LL | 1 => cfg!(not_enable), - | -^^^^^^^^^^^^^^^^^^^^ - | | - | help: try merging the arm patterns: `1 | 0` + | ^^^^^^^^^^^^^^^^^^^^^ | - = help: or try changing either arm body -note: other arm here - --> tests/ui/match_same_arms2.rs:259:9 + = help: try changing either arm body +help: or try merging the arm patterns + | +LL | 1 | 0 => cfg!(not_enable), + | ~~~~~ +help: and remove this obsolete arm + | +LL - 0 => cfg!(not_enable), | -LL | 0 => cfg!(not_enable), - | ^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 13 previous errors diff --git a/tests/ui/match_same_arms_non_exhaustive.fixed b/tests/ui/match_same_arms_non_exhaustive.fixed new file mode 100644 index 0000000000000..804c0a869a9ff --- /dev/null +++ b/tests/ui/match_same_arms_non_exhaustive.fixed @@ -0,0 +1,61 @@ +#![feature(non_exhaustive_omitted_patterns_lint)] +#![warn(clippy::match_same_arms)] +#![no_main] +use std::sync::atomic::Ordering; // #[non_exhaustive] enum + +fn repeat() -> ! { + panic!() +} + +pub fn f(x: Ordering) { + #[deny(non_exhaustive_omitted_patterns)] + match x { + Ordering::Relaxed => println!("relaxed"), + Ordering::Release => println!("release"), + Ordering::Acquire => println!("acquire"), + Ordering::AcqRel | Ordering::SeqCst => repeat(), + _ => repeat(), + } +} + +mod f { + #![deny(non_exhaustive_omitted_patterns)] + + use super::*; + + pub fn f(x: Ordering) { + match x { + Ordering::Relaxed => println!("relaxed"), + Ordering::Release => println!("release"), + Ordering::Acquire => println!("acquire"), + Ordering::AcqRel | Ordering::SeqCst => repeat(), + _ => repeat(), + } + } +} + +// Below should still lint + +pub fn g(x: Ordering) { + match x { + Ordering::Relaxed => println!("relaxed"), + Ordering::Release => println!("release"), + Ordering::Acquire => println!("acquire"), + //~^ ERROR: this match arm has an identical body to the `_` wildcard arm + _ => repeat(), + } +} + +mod g { + use super::*; + + pub fn g(x: Ordering) { + match x { + Ordering::Relaxed => println!("relaxed"), + Ordering::Release => println!("release"), + Ordering::Acquire => println!("acquire"), + //~^ ERROR: this match arm has an identical body to the `_` wildcard arm + _ => repeat(), + } + } +} diff --git a/tests/ui/match_same_arms_non_exhaustive.rs b/tests/ui/match_same_arms_non_exhaustive.rs index 5c277f925a8f1..e50663932a1a6 100644 --- a/tests/ui/match_same_arms_non_exhaustive.rs +++ b/tests/ui/match_same_arms_non_exhaustive.rs @@ -1,7 +1,6 @@ #![feature(non_exhaustive_omitted_patterns_lint)] #![warn(clippy::match_same_arms)] #![no_main] -//@no-rustfix use std::sync::atomic::Ordering; // #[non_exhaustive] enum fn repeat() -> ! { diff --git a/tests/ui/match_same_arms_non_exhaustive.stderr b/tests/ui/match_same_arms_non_exhaustive.stderr index cf2a75354e15c..aa7f8c95dce84 100644 --- a/tests/ui/match_same_arms_non_exhaustive.stderr +++ b/tests/ui/match_same_arms_non_exhaustive.stderr @@ -1,12 +1,13 @@ error: this match arm has an identical body to the `_` wildcard arm - --> tests/ui/match_same_arms_non_exhaustive.rs:45:9 + --> tests/ui/match_same_arms_non_exhaustive.rs:44:9 | -LL | Ordering::AcqRel | Ordering::SeqCst => repeat(), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try removing the arm +LL | / Ordering::AcqRel | Ordering::SeqCst => repeat(), +LL | | + | |________^ help: try removing the arm | = help: or try changing either arm body note: `_` wildcard arm here - --> tests/ui/match_same_arms_non_exhaustive.rs:47:9 + --> tests/ui/match_same_arms_non_exhaustive.rs:46:9 | LL | _ => repeat(), | ^^^^^^^^^^^^^ @@ -14,14 +15,15 @@ LL | _ => repeat(), = help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]` error: this match arm has an identical body to the `_` wildcard arm - --> tests/ui/match_same_arms_non_exhaustive.rs:59:13 + --> tests/ui/match_same_arms_non_exhaustive.rs:58:13 | -LL | Ordering::AcqRel | Ordering::SeqCst => repeat(), - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try removing the arm +LL | / Ordering::AcqRel | Ordering::SeqCst => repeat(), +LL | | + | |____________^ help: try removing the arm | = help: or try changing either arm body note: `_` wildcard arm here - --> tests/ui/match_same_arms_non_exhaustive.rs:61:13 + --> tests/ui/match_same_arms_non_exhaustive.rs:60:13 | LL | _ => repeat(), | ^^^^^^^^^^^^^ diff --git a/tests/ui/missing_const_for_fn/cant_be_const.rs b/tests/ui/missing_const_for_fn/cant_be_const.rs index d026e009684a4..2750e0cdf3f70 100644 --- a/tests/ui/missing_const_for_fn/cant_be_const.rs +++ b/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -161,7 +161,23 @@ union U { f: u32, } -// Do not lint because accessing union fields from const functions is unstable +// Do not lint because accessing union fields from const functions is unstable in 1.55 +#[clippy::msrv = "1.55"] fn h(u: U) -> u32 { unsafe { u.f } } + +mod msrv { + struct Foo(*const u8, *mut u8); + + impl Foo { + #[clippy::msrv = "1.57"] + fn deref_ptr_cannot_be_const(self) -> usize { + unsafe { *self.0 as usize } + } + #[clippy::msrv = "1.58"] + fn deref_mut_ptr_cannot_be_const(self) -> usize { + unsafe { *self.1 as usize } + } + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 12a8320c8f329..06dbbeb31c0d5 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -113,3 +113,31 @@ impl const Drop for D { // Lint this, since it can be dropped in const contexts // FIXME(effects) fn d(this: D) {} + +mod msrv { + struct Foo(*const u8, &'static u8); + + impl Foo { + #[clippy::msrv = "1.58"] + fn deref_ptr_can_be_const(self) -> usize { + //~^ ERROR: this could be a `const fn` + unsafe { *self.0 as usize } + } + + fn deref_copied_val(self) -> usize { + //~^ ERROR: this could be a `const fn` + *self.1 as usize + } + } + + union Bar { + val: u8, + } + + #[clippy::msrv = "1.56"] + fn union_access_can_be_const() { + //~^ ERROR: this could be a `const fn` + let bar = Bar { val: 1 }; + let _ = unsafe { bar.val }; + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 082459fd82120..b2cade3056373 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -102,5 +102,33 @@ LL | | 46 LL | | } | |_^ -error: aborting due to 11 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:122:9 + | +LL | / fn deref_ptr_can_be_const(self) -> usize { +LL | | +LL | | unsafe { *self.0 as usize } +LL | | } + | |_________^ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:127:9 + | +LL | / fn deref_copied_val(self) -> usize { +LL | | +LL | | *self.1 as usize +LL | | } + | |_________^ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:138:5 + | +LL | / fn union_access_can_be_const() { +LL | | +LL | | let bar = Bar { val: 1 }; +LL | | let _ = unsafe { bar.val }; +LL | | } + | |_____^ + +error: aborting due to 14 previous errors diff --git a/tests/ui/missing_panics_doc.rs b/tests/ui/missing_panics_doc.rs index 0e1533fc1ab10..b0fa8e9885988 100644 --- a/tests/ui/missing_panics_doc.rs +++ b/tests/ui/missing_panics_doc.rs @@ -191,3 +191,11 @@ fn from_declared_macro_should_lint_at_macrosite() { // Not here. some_macro_that_panics!() } + +pub fn issue_12760() { + const { + if N == 0 { + panic!(); + } + } +} diff --git a/tests/ui/needless_borrows_for_generic_args.fixed b/tests/ui/needless_borrows_for_generic_args.fixed index bd7a9a0b9840d..5478372cbe00f 100644 --- a/tests/ui/needless_borrows_for_generic_args.fixed +++ b/tests/ui/needless_borrows_for_generic_args.fixed @@ -141,8 +141,8 @@ fn main() { let f = |arg| { let loc = "loc".to_owned(); let _ = std::fs::write("x", &env); // Don't lint. In environment - let _ = std::fs::write("x", arg); - let _ = std::fs::write("x", loc); + let _ = std::fs::write("x", &arg); + let _ = std::fs::write("x", &loc); }; let _ = std::fs::write("x", &env); // Don't lint. Borrowed by `f` f(arg); @@ -158,13 +158,13 @@ fn main() { fn f(_: impl Debug) {} let x = X; - f(&x); // Don't lint. Has significant drop + f(&x); // Don't lint, not copy, passed by a reference to a variable } { fn f(_: impl AsRef) {} let x = String::new(); - f(x); + f(&x); } { fn f(_: impl AsRef) {} @@ -299,4 +299,38 @@ fn main() { check_str(&owner.0); // Don't lint. `owner` can't be partially moved because it impl Drop } } + { + #[derive(Debug)] + struct X(Vec); + + fn f(_: impl Debug) {} + + let x = X(vec![]); + f(&x); // Don't lint, makes x unavailable later + } + { + #[derive(Debug)] + struct X; + + impl Drop for X { + fn drop(&mut self) {} + } + + fn f(_: impl Debug) {} + + #[derive(Debug)] + struct Y(X); + + let y = Y(X); + f(&y); // Don't lint. Not copy, passed by a reference to value + } + { + fn f(_: impl AsRef) {} + let x = String::new(); + f(&x); // Don't lint, not a copy, makes it unavailable later + f(String::new()); // Lint, makes no difference + let y = "".to_owned(); + f(&y); // Don't lint + f("".to_owned()); // Lint + } } diff --git a/tests/ui/needless_borrows_for_generic_args.rs b/tests/ui/needless_borrows_for_generic_args.rs index 5cfd4ce30cc54..2643815d939b5 100644 --- a/tests/ui/needless_borrows_for_generic_args.rs +++ b/tests/ui/needless_borrows_for_generic_args.rs @@ -158,7 +158,7 @@ fn main() { fn f(_: impl Debug) {} let x = X; - f(&x); // Don't lint. Has significant drop + f(&x); // Don't lint, not copy, passed by a reference to a variable } { fn f(_: impl AsRef) {} @@ -299,4 +299,38 @@ fn main() { check_str(&owner.0); // Don't lint. `owner` can't be partially moved because it impl Drop } } + { + #[derive(Debug)] + struct X(Vec); + + fn f(_: impl Debug) {} + + let x = X(vec![]); + f(&x); // Don't lint, makes x unavailable later + } + { + #[derive(Debug)] + struct X; + + impl Drop for X { + fn drop(&mut self) {} + } + + fn f(_: impl Debug) {} + + #[derive(Debug)] + struct Y(X); + + let y = Y(X); + f(&y); // Don't lint. Not copy, passed by a reference to value + } + { + fn f(_: impl AsRef) {} + let x = String::new(); + f(&x); // Don't lint, not a copy, makes it unavailable later + f(&String::new()); // Lint, makes no difference + let y = "".to_owned(); + f(&y); // Don't lint + f(&"".to_owned()); // Lint + } } diff --git a/tests/ui/needless_borrows_for_generic_args.stderr b/tests/ui/needless_borrows_for_generic_args.stderr index 83c076f8d863a..fba0755d14b5f 100644 --- a/tests/ui/needless_borrows_for_generic_args.stderr +++ b/tests/ui/needless_borrows_for_generic_args.stderr @@ -50,28 +50,22 @@ LL | let _ = Command::new("ls").args(&["-a", "-l"]).status().unwrap(); | ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:144:41 - | -LL | let _ = std::fs::write("x", &arg); - | ^^^^ help: change this to: `arg` - -error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:145:41 + --> tests/ui/needless_borrows_for_generic_args.rs:247:13 | -LL | let _ = std::fs::write("x", &loc); - | ^^^^ help: change this to: `loc` +LL | foo(&a); + | ^^ help: change this to: `a` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:167:11 + --> tests/ui/needless_borrows_for_generic_args.rs:331:11 | -LL | f(&x); - | ^^ help: change this to: `x` +LL | f(&String::new()); // Lint, makes no difference + | ^^^^^^^^^^^^^^ help: change this to: `String::new()` error: the borrowed expression implements the required traits - --> tests/ui/needless_borrows_for_generic_args.rs:247:13 + --> tests/ui/needless_borrows_for_generic_args.rs:334:11 | -LL | foo(&a); - | ^^ help: change this to: `a` +LL | f(&"".to_owned()); // Lint + | ^^^^^^^^^^^^^^ help: change this to: `"".to_owned()` -error: aborting due to 12 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/needless_late_init.stderr b/tests/ui/needless_late_init.stderr index 1695784030d02..ce64861fa40a7 100644 --- a/tests/ui/needless_late_init.stderr +++ b/tests/ui/needless_late_init.stderr @@ -8,10 +8,11 @@ LL | a = "zero"; | = note: `-D clippy::needless-late-init` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_late_init)]` -help: declare `a` here +help: move the declaration `a` here + | +LL ~ +LL ~ let a = "zero"; | -LL | let a = "zero"; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:30:5 @@ -22,10 +23,12 @@ LL | let c; LL | b = 1; | ^^^^^ initialised here | -help: declare `b` here +help: move the declaration `b` here + | +LL ~ +LL | let c; +LL ~ let b = 1; | -LL | let b = 1; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:31:5 @@ -36,10 +39,12 @@ LL | b = 1; LL | c = 2; | ^^^^^ initialised here | -help: declare `c` here +help: move the declaration `c` here + | +LL ~ +LL | b = 1; +LL ~ let c = 2; | -LL | let c = 2; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:35:5 @@ -49,10 +54,11 @@ LL | let d: usize; LL | d = 1; | ^^^^^ initialised here | -help: declare `d` here +help: move the declaration `d` here + | +LL ~ +LL ~ let d: usize = 1; | -LL | let d: usize = 1; - | ~~~~~~~~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:38:5 @@ -62,10 +68,11 @@ LL | let e; LL | e = format!("{}", d); | ^^^^^^^^^^^^^^^^^^^^ initialised here | -help: declare `e` here +help: move the declaration `e` here + | +LL ~ +LL ~ let e = format!("{}", d); | -LL | let e = format!("{}", d); - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:43:5 @@ -73,20 +80,17 @@ error: unneeded late initialization LL | let a; | ^^^^^^ | -help: declare `a` here - | -LL | let a = match n { - | +++++++ -help: remove the assignments from the `match` arms +help: move the declaration `a` here and remove the assignments from the `match` arms | +LL ~ +LL | let n = 1; +LL ~ let a = match n { LL ~ 1 => "one", LL | _ => { LL ~ "two" +LL | }, +LL ~ }; | -help: add a semicolon after the `match` expression - | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:52:5 @@ -94,20 +98,15 @@ error: unneeded late initialization LL | let b; | ^^^^^^ | -help: declare `b` here - | -LL | let b = if n == 3 { - | +++++++ -help: remove the assignments from the branches +help: move the declaration `b` here and remove the assignments from the branches | +LL ~ +LL ~ let b = if n == 3 { LL ~ "four" LL | } else { LL ~ "five" +LL ~ }; | -help: add a semicolon after the `if` expression - | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:59:5 @@ -115,20 +114,16 @@ error: unneeded late initialization LL | let d; | ^^^^^^ | -help: declare `d` here - | -LL | let d = if true { - | +++++++ -help: remove the assignments from the branches +help: move the declaration `d` here and remove the assignments from the branches | +LL ~ +LL ~ let d = if true { +LL | let temp = 5; LL ~ temp LL | } else { LL ~ 15 +LL ~ }; | -help: add a semicolon after the `if` expression - | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:67:5 @@ -136,20 +131,15 @@ error: unneeded late initialization LL | let e; | ^^^^^^ | -help: declare `e` here - | -LL | let e = if true { - | +++++++ -help: remove the assignments from the branches +help: move the declaration `e` here and remove the assignments from the branches | +LL ~ +LL ~ let e = if true { LL ~ format!("{} {}", a, b) LL | } else { LL ~ format!("{}", n) +LL ~ }; | -help: add a semicolon after the `if` expression - | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:74:5 @@ -157,14 +147,11 @@ error: unneeded late initialization LL | let f; | ^^^^^^ | -help: declare `f` here - | -LL | let f = match 1 { - | +++++++ -help: remove the assignments from the `match` arms +help: move the declaration `f` here and remove the assignments from the `match` arms | -LL - 1 => f = "three", -LL + 1 => "three", +LL ~ +LL ~ let f = match 1 { +LL ~ 1 => "three", | error: unneeded late initialization @@ -173,19 +160,15 @@ error: unneeded late initialization LL | let g: usize; | ^^^^^^^^^^^^^ | -help: declare `g` here - | -LL | let g: usize = if true { - | ++++++++++++++ -help: remove the assignments from the branches - | -LL - g = 5; -LL + 5 +help: move the declaration `g` here and remove the assignments from the branches | -help: add a semicolon after the `if` expression +LL ~ +LL ~ let g: usize = if true { +LL ~ 5 +LL | } else { +LL | panic!(); +LL ~ }; | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:88:5 @@ -196,10 +179,12 @@ LL | let y = SignificantDrop; LL | x = 1; | ^^^^^ initialised here | -help: declare `x` here +help: move the declaration `x` here + | +LL ~ +LL | let y = SignificantDrop; +LL ~ let x = 1; | -LL | let x = 1; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:92:5 @@ -210,10 +195,12 @@ LL | let y = 1; LL | x = SignificantDrop; | ^^^^^^^^^^^^^^^^^^^ initialised here | -help: declare `x` here +help: move the declaration `x` here + | +LL ~ +LL | let y = 1; +LL ~ let x = SignificantDrop; | -LL | let x = SignificantDrop; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:96:5 @@ -224,10 +211,14 @@ LL | let x; LL | x = SignificantDrop; | ^^^^^^^^^^^^^^^^^^^ initialised here | -help: declare `x` here +help: move the declaration `x` here + | +LL ~ +LL | // types that should be considered insignificant + ... +LL | let y = Box::new(4); +LL ~ let x = SignificantDrop; | -LL | let x = SignificantDrop; - | ~~~~~ error: unneeded late initialization --> tests/ui/needless_late_init.rs:115:5 @@ -235,20 +226,17 @@ error: unneeded late initialization LL | let a; | ^^^^^^ | -help: declare `a` here - | -LL | let a = match n { - | +++++++ -help: remove the assignments from the `match` arms +help: move the declaration `a` here and remove the assignments from the `match` arms | +LL ~ +LL | let n = 1; +LL ~ let a = match n { LL ~ 1 => f().await, LL | _ => { LL ~ "two" +LL | }, +LL ~ }; | -help: add a semicolon after the `match` expression - | -LL | }; - | + error: unneeded late initialization --> tests/ui/needless_late_init.rs:132:5 @@ -256,20 +244,17 @@ error: unneeded late initialization LL | let a; | ^^^^^^ | -help: declare `a` here - | -LL | let a = match n { - | +++++++ -help: remove the assignments from the `match` arms +help: move the declaration `a` here and remove the assignments from the `match` arms | +LL ~ +LL | let n = 1; +LL ~ let a = match n { LL ~ 1 => f(), LL | _ => { LL ~ "two" +LL | }, +LL ~ }; | -help: add a semicolon after the `match` expression - | -LL | }; - | + error: aborting due to 16 previous errors diff --git a/tests/ui/no_effect.rs b/tests/ui/no_effect.rs index dabeda72f0c8c..0ea911c343488 100644 --- a/tests/ui/no_effect.rs +++ b/tests/ui/no_effect.rs @@ -1,6 +1,5 @@ #![feature(fn_traits, unboxed_closures)] #![warn(clippy::no_effect_underscore_binding)] -#![allow(dead_code, path_statements)] #![allow( clippy::deref_addrof, clippy::redundant_field_names, @@ -33,7 +32,6 @@ impl Neg for Cout { } } -struct Unit; struct Tuple(i32); struct Struct { field: i32, @@ -42,10 +40,6 @@ enum Enum { Tuple(i32), Struct { field: i32 }, } -struct DropUnit; -impl Drop for DropUnit { - fn drop(&mut self) {} -} struct DropStruct { field: i32, } @@ -117,15 +111,9 @@ impl FnOnce<(&str,)> for GreetStruct3 { fn main() { let s = get_struct(); - let s2 = get_struct(); 0; //~^ ERROR: statement with no effect - //~| NOTE: `-D clippy::no-effect` implied by `-D warnings` - s2; - //~^ ERROR: statement with no effect - Unit; - //~^ ERROR: statement with no effect Tuple(0); //~^ ERROR: statement with no effect Struct { field: 0 }; @@ -192,7 +180,6 @@ fn main() { unsafe { unsafe_fn() }; let _used = get_struct(); let _x = vec![1]; - DropUnit; DropStruct { field: 0 }; DropTuple(0); DropEnum::Tuple(0); diff --git a/tests/ui/no_effect.stderr b/tests/ui/no_effect.stderr index c7c8eecd054bc..48ec997d938cd 100644 --- a/tests/ui/no_effect.stderr +++ b/tests/ui/no_effect.stderr @@ -1,5 +1,5 @@ error: statement with no effect - --> tests/ui/no_effect.rs:122:5 + --> tests/ui/no_effect.rs:115:5 | LL | 0; | ^^ @@ -8,151 +8,139 @@ LL | 0; = help: to override `-D warnings` add `#[allow(clippy::no_effect)]` error: statement with no effect - --> tests/ui/no_effect.rs:125:5 - | -LL | s2; - | ^^^ - -error: statement with no effect - --> tests/ui/no_effect.rs:127:5 - | -LL | Unit; - | ^^^^^ - -error: statement with no effect - --> tests/ui/no_effect.rs:129:5 + --> tests/ui/no_effect.rs:117:5 | LL | Tuple(0); | ^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:131:5 + --> tests/ui/no_effect.rs:119:5 | LL | Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:133:5 + --> tests/ui/no_effect.rs:121:5 | LL | Struct { ..s }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:135:5 + --> tests/ui/no_effect.rs:123:5 | LL | Union { a: 0 }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:137:5 + --> tests/ui/no_effect.rs:125:5 | LL | Enum::Tuple(0); | ^^^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:139:5 + --> tests/ui/no_effect.rs:127:5 | LL | Enum::Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:141:5 + --> tests/ui/no_effect.rs:129:5 | LL | 5 + 6; | ^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:143:5 + --> tests/ui/no_effect.rs:131:5 | LL | *&42; | ^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:145:5 + --> tests/ui/no_effect.rs:133:5 | LL | &6; | ^^^ error: statement with no effect - --> tests/ui/no_effect.rs:147:5 + --> tests/ui/no_effect.rs:135:5 | LL | (5, 6, 7); | ^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:149:5 + --> tests/ui/no_effect.rs:137:5 | LL | ..; | ^^^ error: statement with no effect - --> tests/ui/no_effect.rs:151:5 + --> tests/ui/no_effect.rs:139:5 | LL | 5..; | ^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:153:5 + --> tests/ui/no_effect.rs:141:5 | LL | ..5; | ^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:155:5 + --> tests/ui/no_effect.rs:143:5 | LL | 5..6; | ^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:157:5 + --> tests/ui/no_effect.rs:145:5 | LL | 5..=6; | ^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:159:5 + --> tests/ui/no_effect.rs:147:5 | LL | [42, 55]; | ^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:161:5 + --> tests/ui/no_effect.rs:149:5 | LL | [42, 55][1]; | ^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:163:5 + --> tests/ui/no_effect.rs:151:5 | LL | (42, 55).1; | ^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:165:5 + --> tests/ui/no_effect.rs:153:5 | LL | [42; 55]; | ^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:167:5 + --> tests/ui/no_effect.rs:155:5 | LL | [42; 55][13]; | ^^^^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:170:5 + --> tests/ui/no_effect.rs:158:5 | LL | || x += 5; | ^^^^^^^^^^ error: statement with no effect - --> tests/ui/no_effect.rs:173:5 + --> tests/ui/no_effect.rs:161:5 | LL | FooString { s: s }; | ^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> tests/ui/no_effect.rs:175:9 + --> tests/ui/no_effect.rs:163:9 | LL | let _unused = 1; | ^^^^^^^ @@ -161,22 +149,22 @@ LL | let _unused = 1; = help: to override `-D warnings` add `#[allow(clippy::no_effect_underscore_binding)]` error: binding to `_` prefixed variable with no side-effect - --> tests/ui/no_effect.rs:178:9 + --> tests/ui/no_effect.rs:166:9 | LL | let _penguin = || println!("Some helpful closure"); | ^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> tests/ui/no_effect.rs:180:9 + --> tests/ui/no_effect.rs:168:9 | LL | let _duck = Struct { field: 0 }; | ^^^^^ error: binding to `_` prefixed variable with no side-effect - --> tests/ui/no_effect.rs:182:9 + --> tests/ui/no_effect.rs:170:9 | LL | let _cat = [2, 4, 6, 8][2]; | ^^^^ -error: aborting due to 29 previous errors +error: aborting due to 27 previous errors diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 0305d895fc585..7fc89bb95380e 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -675,4 +675,60 @@ fn should_not_trigger_on_significant_iterator_drop() { } } +// https://github.com/rust-lang/rust-clippy/issues/9072 +fn should_not_trigger_lint_if_place_expr_has_significant_drop() { + let x = Mutex::new(vec![1, 2, 3]); + let x_guard = x.lock().unwrap(); + + match x_guard[0] { + 1 => println!("1!"), + x => println!("{x}"), + } + + match x_guard.len() { + 1 => println!("1!"), + x => println!("{x}"), + } +} + +struct Guard<'a, T>(MutexGuard<'a, T>); + +struct Ref<'a, T>(&'a T); + +impl<'a, T> Guard<'a, T> { + fn guard(&self) -> &MutexGuard { + &self.0 + } + + fn guard_ref(&self) -> Ref> { + Ref(&self.0) + } + + fn take(self) -> Box> { + Box::new(self.0) + } +} + +fn should_not_trigger_for_significant_drop_ref() { + let mutex = Mutex::new(vec![1, 2]); + let guard = Guard(mutex.lock().unwrap()); + + match guard.guard().len() { + 0 => println!("empty"), + _ => println!("not empty"), + } + + match guard.guard_ref().0.len() { + 0 => println!("empty"), + _ => println!("not empty"), + } + + match guard.take().len() { + //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + 0 => println!("empty"), + _ => println!("not empty"), + }; +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 7d5b1acc7f00a..811bb06552792 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -28,6 +28,9 @@ LL | match s.lock_m().get_the_value() { LL | println!("{}", s.lock_m().get_the_value()); | ---------- another value with significant `Drop` created here ... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... LL | } | - temporary lives until here | @@ -47,6 +50,9 @@ LL | match s.lock_m_m().get_the_value() { LL | println!("{}", s.lock_m().get_the_value()); | ---------- another value with significant `Drop` created here ... +LL | println!("{}", s.lock_m().get_the_value()); + | ---------- another value with significant `Drop` created here +... LL | } | - temporary lives until here | @@ -360,7 +366,7 @@ LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | _ => println!("Value is {}", s.lock().deref()), - | ---------------- another value with significant `Drop` created here + | -------- another value with significant `Drop` created here LL | }; | - temporary lives until here | @@ -378,7 +384,7 @@ LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | matcher => println!("Value is {}", s.lock().deref()), - | ---------------- another value with significant `Drop` created here + | -------- another value with significant `Drop` created here LL | _ => println!("Value was not a match"), LL | }; | - temporary lives until here @@ -499,5 +505,21 @@ LL ~ let value = mutex.lock().unwrap().foo(); LL ~ match value { | -error: aborting due to 26 previous errors +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> tests/ui/significant_drop_in_scrutinee.rs:726:11 + | +LL | match guard.take().len() { + | ^^^^^^^^^^^^^^^^^^ +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = guard.take().len(); +LL ~ match value { + | + +error: aborting due to 27 previous errors diff --git a/tests/ui/unnecessary_iter_cloned.fixed b/tests/ui/unnecessary_iter_cloned.fixed index ad0e5fab029ee..2c582c90ba8ce 100644 --- a/tests/ui/unnecessary_iter_cloned.fixed +++ b/tests/ui/unnecessary_iter_cloned.fixed @@ -21,6 +21,8 @@ fn main() { let _ = check_files_ref_mut(&[(FileType::Account, path)]); let _ = check_files_self_and_arg(&[(FileType::Account, path)]); let _ = check_files_mut_path_buf(&[(FileType::Account, std::path::PathBuf::new())]); + + check_mut_iteratee_and_modify_inner_variable(); } // `check_files` and its variants are based on: @@ -138,3 +140,33 @@ fn check_files_mut_path_buf(files: &[(FileType, std::path::PathBuf)]) -> bool { fn get_file_path(_file_type: &FileType) -> Result { Ok(std::path::PathBuf::new()) } + +// Issue 12098 +// https://github.com/rust-lang/rust-clippy/issues/12098 +// no message emits +fn check_mut_iteratee_and_modify_inner_variable() { + struct Test { + list: Vec, + mut_this: bool, + } + + impl Test { + fn list(&self) -> &[String] { + &self.list + } + } + + let mut test = Test { + list: vec![String::from("foo"), String::from("bar")], + mut_this: false, + }; + + for _item in test.list().to_vec() { + println!("{}", _item); + + test.mut_this = true; + { + test.mut_this = true; + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.rs b/tests/ui/unnecessary_iter_cloned.rs index d3d59c4c70f5c..a28ccd1efef26 100644 --- a/tests/ui/unnecessary_iter_cloned.rs +++ b/tests/ui/unnecessary_iter_cloned.rs @@ -21,6 +21,8 @@ fn main() { let _ = check_files_ref_mut(&[(FileType::Account, path)]); let _ = check_files_self_and_arg(&[(FileType::Account, path)]); let _ = check_files_mut_path_buf(&[(FileType::Account, std::path::PathBuf::new())]); + + check_mut_iteratee_and_modify_inner_variable(); } // `check_files` and its variants are based on: @@ -138,3 +140,33 @@ fn check_files_mut_path_buf(files: &[(FileType, std::path::PathBuf)]) -> bool { fn get_file_path(_file_type: &FileType) -> Result { Ok(std::path::PathBuf::new()) } + +// Issue 12098 +// https://github.com/rust-lang/rust-clippy/issues/12098 +// no message emits +fn check_mut_iteratee_and_modify_inner_variable() { + struct Test { + list: Vec, + mut_this: bool, + } + + impl Test { + fn list(&self) -> &[String] { + &self.list + } + } + + let mut test = Test { + list: vec![String::from("foo"), String::from("bar")], + mut_this: false, + }; + + for _item in test.list().to_vec() { + println!("{}", _item); + + test.mut_this = true; + { + test.mut_this = true; + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.stderr b/tests/ui/unnecessary_iter_cloned.stderr index 9d3591e0dbfc8..fb98cfddc262f 100644 --- a/tests/ui/unnecessary_iter_cloned.stderr +++ b/tests/ui/unnecessary_iter_cloned.stderr @@ -1,5 +1,5 @@ error: unnecessary use of `copied` - --> tests/ui/unnecessary_iter_cloned.rs:29:22 + --> tests/ui/unnecessary_iter_cloned.rs:31:22 | LL | for (t, path) in files.iter().copied() { | ^^^^^^^^^^^^^^^^^^^^^ @@ -17,7 +17,7 @@ LL + let other = match get_file_path(t) { | error: unnecessary use of `copied` - --> tests/ui/unnecessary_iter_cloned.rs:44:22 + --> tests/ui/unnecessary_iter_cloned.rs:46:22 | LL | for (t, path) in files.iter().copied() { | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/useless_attribute.fixed b/tests/ui/useless_attribute.fixed index 81759086f79e4..231fc0a892ad1 100644 --- a/tests/ui/useless_attribute.fixed +++ b/tests/ui/useless_attribute.fixed @@ -86,8 +86,51 @@ mod module { #[rustfmt::skip] #[allow(unused_import_braces)] +#[allow(unused_braces)] use module::{Struct}; fn main() { test_indented_attr(); } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/4467 +#[allow(dead_code)] +use std::collections as puppy_doggy; + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/11595 +pub mod hidden_glob_reexports { + #![allow(unreachable_pub)] + + mod my_prelude { + pub struct MyCoolTypeInternal; + pub use MyCoolTypeInternal as MyCoolType; + } + + mod my_uncool_type { + pub(crate) struct MyUncoolType; + } + + // This exports `MyCoolType`. + pub use my_prelude::*; + + // This hides `my_prelude::MyCoolType`. + #[allow(hidden_glob_reexports)] + use my_uncool_type::MyUncoolType as MyCoolType; +} + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/10878 +pub mod ambiguous_glob_exports { + #![allow(unreachable_pub)] + + mod my_prelude { + pub struct MyType; + } + + mod my_type { + pub struct MyType; + } + + #[allow(ambiguous_glob_reexports)] + pub use my_prelude::*; + pub use my_type::*; +} diff --git a/tests/ui/useless_attribute.rs b/tests/ui/useless_attribute.rs index 59a9dcf093bca..8dfcd2110a4bb 100644 --- a/tests/ui/useless_attribute.rs +++ b/tests/ui/useless_attribute.rs @@ -86,8 +86,51 @@ mod module { #[rustfmt::skip] #[allow(unused_import_braces)] +#[allow(unused_braces)] use module::{Struct}; fn main() { test_indented_attr(); } + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/4467 +#[allow(dead_code)] +use std::collections as puppy_doggy; + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/11595 +pub mod hidden_glob_reexports { + #![allow(unreachable_pub)] + + mod my_prelude { + pub struct MyCoolTypeInternal; + pub use MyCoolTypeInternal as MyCoolType; + } + + mod my_uncool_type { + pub(crate) struct MyUncoolType; + } + + // This exports `MyCoolType`. + pub use my_prelude::*; + + // This hides `my_prelude::MyCoolType`. + #[allow(hidden_glob_reexports)] + use my_uncool_type::MyUncoolType as MyCoolType; +} + +// Regression test for https://github.com/rust-lang/rust-clippy/issues/10878 +pub mod ambiguous_glob_exports { + #![allow(unreachable_pub)] + + mod my_prelude { + pub struct MyType; + } + + mod my_type { + pub struct MyType; + } + + #[allow(ambiguous_glob_reexports)] + pub use my_prelude::*; + pub use my_type::*; +} diff --git a/tests/ui/while_float.rs b/tests/ui/while_float.rs new file mode 100644 index 0000000000000..a3b0618948e6b --- /dev/null +++ b/tests/ui/while_float.rs @@ -0,0 +1,14 @@ +#[deny(clippy::while_float)] +fn main() { + let mut x = 0.0_f32; + while x < 42.0_f32 { + x += 0.5; + } + while x < 42.0 { + x += 1.0; + } + let mut x = 0; + while x < 42 { + x += 1; + } +} diff --git a/tests/ui/while_float.stderr b/tests/ui/while_float.stderr new file mode 100644 index 0000000000000..b8e934b97c6c4 --- /dev/null +++ b/tests/ui/while_float.stderr @@ -0,0 +1,20 @@ +error: while condition comparing floats + --> tests/ui/while_float.rs:4:11 + | +LL | while x < 42.0_f32 { + | ^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/while_float.rs:1:8 + | +LL | #[deny(clippy::while_float)] + | ^^^^^^^^^^^^^^^^^^^ + +error: while condition comparing floats + --> tests/ui/while_float.rs:7:11 + | +LL | while x < 42.0 { + | ^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index c63edd5bf7098..7fd779fe9a46e 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -406,7 +406,7 @@ } // Search by id - if (lint.id.indexOf(searchStr.replace("-", "_")) !== -1) { + if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { return true; } From 7439ecb07c85bcb148b3b62b2de0be746d5beac2 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Tue, 21 May 2024 22:21:33 +0100 Subject: [PATCH 19/34] Added check for type unification with the iter --- .../src/methods/iter_on_single_or_empty_collections.rs | 10 +++++++--- tests/ui/iter_on_empty_collections.fixed | 4 ++++ tests/ui/iter_on_empty_collections.rs | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 1bf323cf3c7df..f4397212cf660 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -37,14 +37,18 @@ fn is_arg_ty_unified_in_fn<'tcx>( ) -> bool { let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); - let arg_ty_in_args = fn_sig.input(arg_id_in_args); + let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder(); cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { clause .as_projection_clause() .and_then(|p| p.map_bound(|p| p.term.ty()).transpose()) - .is_some_and(|ty| ty == arg_ty_in_args) - }) + .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) + }) || fn_sig + .inputs() + .iter() + .enumerate() + .any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args))) } pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) { diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed index 4b5746c7b6f48..0f28b48d9ab8b 100644 --- a/tests/ui/iter_on_empty_collections.fixed +++ b/tests/ui/iter_on_empty_collections.fixed @@ -38,6 +38,10 @@ fn array() { for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { println!("{i}"); } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs index 737192d556d30..702da514df7d2 100644 --- a/tests/ui/iter_on_empty_collections.rs +++ b/tests/ui/iter_on_empty_collections.rs @@ -38,6 +38,10 @@ fn array() { for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { println!("{i}"); } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { From 038f6179d746afb9fe396395ece667d7a0a4052c Mon Sep 17 00:00:00 2001 From: WeiTheShinobi Date: Sat, 2 Mar 2024 16:55:38 +0800 Subject: [PATCH 20/34] bug fix: lint numbered_fields message error --- clippy_lints/src/init_numbered_fields.rs | 4 ++-- tests/ui/numbered_fields.fixed | 5 +++++ tests/ui/numbered_fields.rs | 5 +++++ tests/ui/numbered_fields.stderr | 8 +++++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index e486563808a5d..1c8fd0a27f98f 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields { snippet_with_applicability(cx, path.span(), "..", &mut appl), expr_spans .into_iter_sorted() - .map(|(_, span)| snippet_with_applicability(cx, span, "..", &mut appl)) + .map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) .intersperse(Cow::Borrowed(", ")) .collect::() ); diff --git a/tests/ui/numbered_fields.fixed b/tests/ui/numbered_fields.fixed index dc88081ba0a71..108520eed38d5 100644 --- a/tests/ui/numbered_fields.fixed +++ b/tests/ui/numbered_fields.fixed @@ -34,4 +34,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec(vec![0, 1, 2, 3]); } diff --git a/tests/ui/numbered_fields.rs b/tests/ui/numbered_fields.rs index e8fa652e3c1da..c718661a68263 100644 --- a/tests/ui/numbered_fields.rs +++ b/tests/ui/numbered_fields.rs @@ -42,4 +42,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; } diff --git a/tests/ui/numbered_fields.stderr b/tests/ui/numbered_fields.stderr index 96426cab1e623..9d3f59cd3769e 100644 --- a/tests/ui/numbered_fields.stderr +++ b/tests/ui/numbered_fields.stderr @@ -23,5 +23,11 @@ LL | | 1: 3u32, LL | | }; | |_____^ help: try: `TupleStruct(1u32, 3u32, 2u8)` -error: aborting due to 2 previous errors +error: used a field initializer for a tuple struct + --> tests/ui/numbered_fields.rs:49:13 + | +LL | let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `TupleStructVec(vec![0, 1, 2, 3])` + +error: aborting due to 3 previous errors From 765baba165adb0ce0bef398f5eed54ba97d638f3 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 10 May 2024 17:29:02 +0300 Subject: [PATCH 21/34] rustc: Use `tcx.used_crates(())` more And explain when it should be used. --- clippy_utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 4c603bda770a9..94d4656377f98 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -647,7 +647,7 @@ fn item_children_by_name(tcx: TyCtxt<'_>, def_id: DefId, name: Symbol) -> Vec, path: &[&str]) -> Vec { fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> impl Iterator + '_ { - tcx.crates(()) + tcx.crates_including_speculative(()) .iter() .copied() .filter(move |&num| tcx.crate_name(num) == name) From 6641f9f6e138a3166b06fc27d62e7c349e1f927d Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Sat, 27 Apr 2024 23:47:11 +0800 Subject: [PATCH 22/34] Track lifetime on values with significant drop --- .../matches/significant_drop_in_scrutinee.rs | 418 ++++++++++-------- tests/ui/significant_drop_in_scrutinee.rs | 98 +++- tests/ui/significant_drop_in_scrutinee.stderr | 206 +++++---- 3 files changed, 426 insertions(+), 296 deletions(-) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 6ac0705abb212..2f72e59834fa6 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -1,12 +1,17 @@ +use std::ops::ControlFlow; + use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{get_attr, is_lint_allowed}; +use itertools::Itertools; +use rustc_ast::Mutability; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LintContext}; -use rustc_middle::ty::{GenericArgKind, Ty}; +use rustc_middle::ty::{GenericArgKind, Region, RegionKind, Ty, TyCtxt, TypeVisitable, TypeVisitor}; use rustc_span::Span; use super::SIGNIFICANT_DROP_IN_SCRUTINEE; @@ -22,43 +27,34 @@ pub(super) fn check<'tcx>( return; } - if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) { - for found in suggestions { - span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { - set_diagnostic(diag, cx, expr, found); - let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); - diag.span_label(s, "temporary lives until here"); - for span in has_significant_drop_in_arms(cx, arms) { - diag.span_label(span, "another value with significant `Drop` created here"); - } - diag.note("this might lead to deadlocks or other unexpected behavior"); - }); - } + let (suggestions, message) = has_significant_drop_in_scrutinee(cx, scrutinee, source); + for found in suggestions { + span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { + set_diagnostic(diag, cx, expr, found); + let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); + diag.span_label(s, "temporary lives until here"); + for span in has_significant_drop_in_arms(cx, arms) { + diag.span_label(span, "another value with significant `Drop` created here"); + } + diag.note("this might lead to deadlocks or other unexpected behavior"); + }); } } fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { - if found.lint_suggestion == LintSuggestion::MoveAndClone { - // If our suggestion is to move and clone, then we want to leave it to the user to - // decide how to address this lint, since it may be that cloning is inappropriate. - // Therefore, we won't to emit a suggestion. - return; - } - let original = snippet(cx, found.found_span, ".."); let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); - let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy { - format!("let value = *{original};\n{trailing_indent}") - } else if found.is_unit_return_val { - // If the return value of the expression to be moved is unit, then we don't need to - // capture the result in a temporary -- we can just replace it completely with `()`. - format!("{original};\n{trailing_indent}") - } else { - format!("let value = {original};\n{trailing_indent}") + let replacement = { + let (def_part, deref_part) = if found.is_unit_return_val { + ("", String::new()) + } else { + ("let value = ", "*".repeat(found.peel_ref_times)) + }; + format!("{def_part}{deref_part}{original};\n{trailing_indent}") }; - let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly { + let suggestion_message = if found.peel_ref_times == 0 { "try moving the temporary above the match" } else { "try moving the temporary above the match and create a copy" @@ -66,8 +62,11 @@ fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: & let scrutinee_replacement = if found.is_unit_return_val { "()".to_owned() - } else { + } else if found.peel_ref_times == 0 { "value".to_owned() + } else { + let ref_part = "&".repeat(found.peel_ref_times); + format!("({ref_part}value)") }; diag.multipart_suggestion( @@ -86,20 +85,18 @@ fn has_significant_drop_in_scrutinee<'tcx>( cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>, source: MatchSource, -) -> Option<(Vec, &'static str)> { +) -> (Vec, &'static str) { let mut helper = SigDropHelper::new(cx); let scrutinee = match (source, &scrutinee.kind) { (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, _ => scrutinee, }; - helper.find_sig_drop(scrutinee).map(|drops| { - let message = if source == MatchSource::Normal { - "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" - } else { - "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" - }; - (drops, message) - }) + let message = if source == MatchSource::Normal { + "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" + } else { + "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" + }; + (helper.find_sig_drop(scrutinee), message) } struct SigDropChecker<'a, 'tcx> { @@ -172,205 +169,248 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +enum SigDropHolder { + /// No values with significant drop present in this expression. + /// + /// Expressions that we've emited lints do not count. + None, + /// Some field in this expression references to values with significant drop. + /// + /// Example: `(1, &data.lock().field)`. + PackedRef, + /// The value of this expression references to values with significant drop. + /// + /// Example: `data.lock().field`. + DirectRef, + /// This expression should be moved out to avoid significant drop in scrutinee. + Moved, +} + +impl Default for SigDropHolder { + fn default() -> Self { + Self::None + } +} + struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_chain_end: bool, - has_significant_drop: bool, - current_sig_drop: Option, - sig_drop_spans: Option>, - special_handling_for_binary_op: bool, + parent_expr: Option<&'tcx Expr<'tcx>>, + sig_drop_holder: SigDropHolder, + sig_drop_spans: Vec, sig_drop_checker: SigDropChecker<'a, 'tcx>, } -#[expect(clippy::enum_variant_names)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum LintSuggestion { - MoveOnly, - MoveAndDerefToCopy, - MoveAndClone, -} - -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] struct FoundSigDrop { found_span: Span, is_unit_return_val: bool, - lint_suggestion: LintSuggestion, + peel_ref_times: usize, } impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { SigDropHelper { cx, - is_chain_end: true, - has_significant_drop: false, - current_sig_drop: None, - sig_drop_spans: None, - special_handling_for_binary_op: false, + parent_expr: None, + sig_drop_holder: SigDropHolder::None, + sig_drop_spans: Vec::new(), sig_drop_checker: SigDropChecker::new(cx), } } - fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option> { + fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Vec { self.visit_expr(match_expr); - // If sig drop spans is empty but we found a significant drop, it means that we didn't find - // a type that was trivially copyable as we moved up the chain after finding a significant - // drop, so move the entire scrutinee. - if self.has_significant_drop && self.sig_drop_spans.is_none() { - self.try_setting_current_suggestion(match_expr, true); - self.move_current_suggestion(); - } - - self.sig_drop_spans.take() + core::mem::take(&mut self.sig_drop_spans) } - fn replace_current_sig_drop( - &mut self, - found_span: Span, - is_unit_return_val: bool, - lint_suggestion: LintSuggestion, - ) { - self.current_sig_drop.replace(FoundSigDrop { + fn replace_current_sig_drop(&mut self, found_span: Span, is_unit_return_val: bool, peel_ref_times: usize) { + self.sig_drop_spans.clear(); + self.sig_drop_spans.push(FoundSigDrop { found_span, is_unit_return_val, - lint_suggestion, + peel_ref_times, }); } - /// This will try to set the current suggestion (so it can be moved into the suggestions vec - /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us - /// an opportunity to look for another type in the chain that will be trivially copyable. - /// However, if we are at the end of the chain, we want to accept whatever is there. (The - /// suggestion won't actually be output, but the diagnostic message will be output, so the user - /// can determine the best way to handle the lint.) - fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) { - if self.current_sig_drop.is_some() { - return; + fn try_move_sig_drop(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + if self.sig_drop_holder == SigDropHolder::Moved { + self.sig_drop_holder = SigDropHolder::None; } - let ty = self.cx.typeck_results().expr_ty(expr); - if ty.is_ref() { - // We checked that the type was ref, so builtin_deref will return Some, - // but let's avoid any chance of an ICE. - if let Some(ty) = ty.builtin_deref(true) { - if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); - } + + if self.sig_drop_holder == SigDropHolder::DirectRef { + self.sig_drop_holder = SigDropHolder::PackedRef; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } else if self.sig_drop_checker.is_sig_drop_expr(expr) { + // The values with significant drop can be moved to some other functions. For example, consider + // `drop(data.lock())`. We use `SigDropHolder::None` here to avoid emitting lints in such scenarios. + self.sig_drop_holder = SigDropHolder::None; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } + + if self.sig_drop_holder != SigDropHolder::None { + let parent_ty = self.cx.typeck_results().expr_ty(parent_expr); + if !ty_has_erased_regions(parent_ty) && !parent_expr.is_syntactic_place_expr() { + self.replace_current_sig_drop(parent_expr.span, parent_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } + + let (peel_ref_ty, peel_ref_times) = ty_peel_refs(parent_ty); + if !ty_has_erased_regions(peel_ref_ty) && is_copy(self.cx, peel_ref_ty) { + self.replace_current_sig_drop(parent_expr.span, peel_ref_ty.is_unit(), peel_ref_times); + self.sig_drop_holder = SigDropHolder::Moved; } - } else if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); } } - fn move_current_suggestion(&mut self) { - if let Some(current) = self.current_sig_drop.take() { - self.sig_drop_spans.get_or_insert_with(Vec::new).push(current); + fn try_move_sig_drop_direct_ref(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + let arg_idx = match parent_expr.kind { + ExprKind::MethodCall(_, receiver, exprs, _) => std::iter::once(receiver) + .chain(exprs.iter()) + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Call(_, exprs) => exprs + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Binary(_, lhs, rhs) | ExprKind::AssignOp(_, lhs, rhs) => [lhs, rhs] + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Unary(_, ex) => (ex.hir_id == expr.hir_id).then_some(0), + _ => { + // Here we assume that all other expressions create or propagate the reference to the value with + // significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + return; + }, + }; + let Some(arg_idx) = arg_idx else { + return; + }; + + let fn_sig = if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) { + self.cx.tcx.fn_sig(def_id).instantiate_identity() + } else { + return; + }; + + let input_re = if let Some(input_ty) = fn_sig.skip_binder().inputs().get(arg_idx) + && let rustc_middle::ty::Ref(input_re, _, _) = input_ty.kind() + { + input_re + } else { + return; + }; + + // Late bound lifetime parameters are not related to any constraints, so we can track them in a very + // simple manner. For other lifetime parameters, we give up and update the state to `PackedRef`. + let RegionKind::ReBound(_, input_re_bound) = input_re.kind() else { + self.sig_drop_holder = SigDropHolder::PackedRef; + return; + }; + let contains_input_re = |re_bound| { + if re_bound == input_re_bound { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }; + + let output_ty = fn_sig.skip_binder().output(); + if let rustc_middle::ty::Ref(output_re, peel_ref_ty, _) = output_ty.kind() + && input_re == output_re + && for_each_top_level_late_bound_region(*peel_ref_ty, contains_input_re).is_continue() + { + // We're lucky! The output type is still a direct reference to the value with significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + } else if for_each_top_level_late_bound_region(output_ty, contains_input_re).is_continue() { + // The lifetime to the value with significant drop goes away. So we can emit a lint that suggests to + // move the expression out. + self.replace_current_sig_drop(parent_expr.span, output_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } else { + // TODO: The lifetime is still there but it's for a inner type. For instance, consider + // `Some(&mutex.lock().field)`, which has a type of `Option<&u32>`. How to address this scenario? + self.sig_drop_holder = SigDropHolder::PackedRef; } } +} - fn visit_exprs_for_binary_ops( - &mut self, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, - is_unit_return_val: bool, - span: Span, - ) { - self.special_handling_for_binary_op = true; - self.visit_expr(left); - self.visit_expr(right); - - // If either side had a significant drop, suggest moving the entire scrutinee to avoid - // unnecessary copies and to simplify cases where both sides have significant drops. - if self.has_significant_drop { - self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly); - } +fn ty_peel_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { + let mut n = 0; + while let rustc_middle::ty::Ref(_, new_ty, Mutability::Not) = ty.kind() { + ty = *new_ty; + n += 1; + } + (ty, n) +} + +fn ty_has_erased_regions(ty: Ty<'_>) -> bool { + struct V; - self.special_handling_for_binary_op = false; + impl<'tcx> TypeVisitor> for V { + type Result = ControlFlow<()>; + + fn visit_region(&mut self, region: Region<'tcx>) -> Self::Result { + if region.is_erased() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } } + + ty.visit_with(&mut V).is_break() } impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - if !self.is_chain_end && self.sig_drop_checker.is_sig_drop_expr(ex) { - self.has_significant_drop = true; + // We've emited a lint on some neighborhood expression. That lint will suggest to move out the + // _parent_ expression (not the expression itself). Since we decide to move out the parent + // expression, it is pointless to continue to process the current expression. + if self.sig_drop_holder == SigDropHolder::Moved { return; } - self.is_chain_end = false; + + // These states are of neighborhood expressions. We save and clear them here, and we'll later merge + // the states of the current expression with them at the end of the method. + let sig_drop_holder_before = core::mem::take(&mut self.sig_drop_holder); + let sig_drop_spans_before = core::mem::take(&mut self.sig_drop_spans); + let parent_expr_before = self.parent_expr.replace(ex); match ex.kind { - ExprKind::MethodCall(_, expr, ..) => { - self.visit_expr(expr); - } - ExprKind::Binary(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, false, ex.span); - } - ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, true, ex.span); - } - ExprKind::Tup(exprs) => { - for expr in exprs { - self.visit_expr(expr); - if self.has_significant_drop { - // We may have not have set current_sig_drop if all the suggestions were - // MoveAndClone, so add this tuple item's full expression in that case. - if self.current_sig_drop.is_none() { - self.try_setting_current_suggestion(expr, true); - } - - // Now we are guaranteed to have something, so add it to the final vec. - self.move_current_suggestion(); - } - // Reset `has_significant_drop` after each tuple expression so we can look for - // additional cases. - self.has_significant_drop = false; - } - if self.sig_drop_spans.is_some() { - self.has_significant_drop = true; - } - } - ExprKind::Array(..) | - ExprKind::Call(..) | - ExprKind::Unary(..) | - ExprKind::If(..) | - ExprKind::Match(..) | - ExprKind::Field(..) | - ExprKind::Index(..) | - ExprKind::Ret(..) | - ExprKind::Become(..) | - ExprKind::Repeat(..) | - ExprKind::Yield(..) => walk_expr(self, ex), - ExprKind::AddrOf(_, _, _) | - ExprKind::Block(_, _) | - ExprKind::Break(_, _) | - ExprKind::Cast(_, _) | - // Don't want to check the closure itself, only invocation, which is covered by MethodCall - ExprKind::Closure { .. } | - ExprKind::ConstBlock(_) | - ExprKind::Continue(_) | - ExprKind::DropTemps(_) | - ExprKind::Err(_) | - ExprKind::InlineAsm(_) | - ExprKind::OffsetOf(_, _) | - ExprKind::Let(_) | - ExprKind::Lit(_) | - ExprKind::Loop(_, _, _, _) | - ExprKind::Path(_) | - ExprKind::Struct(_, _, _) | - ExprKind::Type(_, _) => { - return; + // Skip blocks because values in blocks will be dropped as usual. + ExprKind::Block(..) => (), + _ => walk_expr(self, ex), + } + + if let Some(parent_ex) = parent_expr_before { + match parent_ex.kind { + ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) + if lhs.hir_id == ex.hir_id && self.sig_drop_holder == SigDropHolder::Moved => + { + // Never move out only the assignee. Instead, we should always move out the whole assigment. + self.replace_current_sig_drop(parent_ex.span, true, 0); + }, + _ => { + self.try_move_sig_drop(ex, parent_ex); + }, } } - // Once a significant temporary has been found, we need to go back up at least 1 level to - // find the span to extract for replacement, so the temporary gets dropped. However, for - // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to - // simplify cases where both sides have significant drops. - if self.has_significant_drop && !self.special_handling_for_binary_op { - self.try_setting_current_suggestion(ex, false); + self.sig_drop_holder = std::cmp::max(self.sig_drop_holder, sig_drop_holder_before); + + // We do not need those old spans in neighborhood expressions if we emit a lint that suggests to + // move out the _parent_ expression (i.e., `self.sig_drop_holder == SigDropHolder::Moved`). + if self.sig_drop_holder != SigDropHolder::Moved { + let mut sig_drop_spans = sig_drop_spans_before; + sig_drop_spans.append(&mut self.sig_drop_spans); + self.sig_drop_spans = sig_drop_spans; } + + self.parent_expr = parent_expr_before; } } diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 7fc89bb95380e..8ee15440ccf01 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -274,11 +274,20 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }, (_, _, _) => {}, }; + } +} + +// Should not trigger lint since `String::as_str` returns a reference (i.e., `&str`) +// to the locked data (i.e., the `String`) and it is not surprising that matching such +// a reference needs to keep the data locked until the end of the match block. +fn should_not_trigger_lint_for_string_as_str() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + { + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() }); + match mutex3.lock().unwrap().s.as_str() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior "three" => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -289,8 +298,6 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }; match (true, mutex3.lock().unwrap().s.as_str()) { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior (_, "three") => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -514,16 +521,15 @@ impl StateStringWithBoxedMutexGuard { } } -fn should_trigger_lint_for_boxed_mutex_guard_holding_string() { +fn should_not_trigger_lint_for_string_ref() { let s = StateStringWithBoxedMutexGuard::new(); let matcher = String::from("A String"); - // Should trigger lint because a temporary Box holding a type with a significant drop in a match - // scrutinee may have a potentially surprising lifetime. + // Should not trigger lint because the second `deref` returns a string reference (`&String`). + // So it is not surprising that matching the reference implies that the lock needs to be held + // until the end of the block. match s.lock().deref().deref() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the - //~| NOTE: this might lead to deadlocks or other unexpected behavior matcher => println!("Value is {}", s.lock().deref()), _ => println!("Value was not a match"), }; @@ -639,13 +645,12 @@ fn should_trigger_lint_for_non_ref_move_and_clone_suggestion() { }; } -fn should_trigger_lint_for_read_write_lock_for_loop() { - // For-in loops desugar to match expressions and are prone to the type of deadlock this lint is - // designed to look for. +fn should_not_trigger_lint_for_read_write_lock_for_loop() { let rwlock = RwLock::>::new(vec!["1".to_string()]); + // Should not trigger lint. Since we're iterating over the data, it's obvious that the lock + // has to be held until the iteration finishes. + // https://github.com/rust-lang/rust-clippy/issues/8987 for s in rwlock.read().unwrap().iter() { - //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior println!("{}", s); } } @@ -731,4 +736,69 @@ fn should_not_trigger_for_significant_drop_ref() { }; } +struct Foo<'a>(&'a Vec); + +impl<'a> Foo<'a> { + fn copy_old_lifetime(&self) -> &'a Vec { + self.0 + } + + fn reborrow_new_lifetime(&self) -> &Vec { + self.0 + } +} + +fn should_trigger_lint_if_and_only_if_lifetime_is_irrelevant() { + let vec = Vec::new(); + let mutex = Mutex::new(Foo(&vec)); + + // Should trigger lint even if `copy_old_lifetime()` has a lifetime, as the lifetime of + // `&vec` is unrelated to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().copy_old_lifetime() { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{}", val); + } + + // Should not trigger lint because `reborrow_new_lifetime()` has a lifetime and the + // lifetime is related to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().reborrow_new_lifetime() { + println!("{}", val); + } +} + +fn should_not_trigger_lint_for_complex_lifetime() { + let mutex = Mutex::new(vec!["hello".to_owned()]); + let string = "world".to_owned(); + + // Should not trigger lint due to the relevant lifetime. + for c in mutex.lock().unwrap().first().unwrap().chars() { + println!("{}", c); + } + + // Should trigger lint due to the irrelevant lifetime. + // + // FIXME: The lifetime is too complex to analyze. In order to avoid false positives, we do not + // trigger lint. + for c in mutex.lock().unwrap().first().map(|_| &string).unwrap().chars() { + println!("{}", c); + } +} + +fn should_not_trigger_lint_with_explicit_drop() { + let mutex = Mutex::new(vec![1]); + + // Should not trigger lint since the drop explicitly happens. + for val in [drop(mutex.lock().unwrap()), ()] { + println!("{:?}", val); + } + + // Should trigger lint if there is no explicit drop. + for val in [mutex.lock().unwrap()[0], 2] { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{:?}", val); + } +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 811bb06552792..4a483e79d8add 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -160,45 +160,53 @@ LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:279:15 + --> tests/ui/significant_drop_in_scrutinee.rs:319:11 | -LL | match mutex3.lock().unwrap().s.as_str() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex.lock().unwrap().s.len() > 1 { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here ... -LL | }; - | - temporary lives until here +LL | }; + | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match value > 1 { + | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:291:22 + --> tests/ui/significant_drop_in_scrutinee.rs:328:15 | -LL | match (true, mutex3.lock().unwrap().s.as_str()) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match 1 < mutex.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here +LL | mutex.lock().unwrap().s.len(); + | --------------------- another value with significant `Drop` created here ... -LL | }; - | - temporary lives until here +LL | }; + | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match 1 < value { + | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:312:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:11 | -LL | match mutex.lock().unwrap().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex.lock().unwrap().s.len(); - | --------------------- another value with significant `Drop` created here +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here ... LL | }; | - temporary lives until here @@ -206,18 +214,20 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex.lock().unwrap().s.len() > 1; -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value < mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:321:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:44 | -LL | match 1 < mutex.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... -LL | mutex.lock().unwrap().s.len(); - | --------------------- another value with significant `Drop` created here +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here ... LL | }; | - temporary lives until here @@ -225,15 +235,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = 1 < mutex.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() < value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:341:11 + --> tests/ui/significant_drop_in_scrutinee.rs:361:11 | -LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -246,15 +256,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value >= mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:354:11 + --> tests/ui/significant_drop_in_scrutinee.rs:361:45 | LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -267,15 +277,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() >= value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:391:11 + --> tests/ui/significant_drop_in_scrutinee.rs:398:11 | LL | match get_mutex_guard().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -286,12 +296,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = get_mutex_guard().s.len() > 1; -LL ~ match value { +LL ~ let value = get_mutex_guard().s.len(); +LL ~ match value > 1 { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:410:11 + --> tests/ui/significant_drop_in_scrutinee.rs:417:11 | LL | match match i { | ___________^ @@ -299,9 +309,9 @@ LL | | LL | | LL | | 100 => mutex1.lock().unwrap(), ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -319,13 +329,12 @@ LL + 100 => mutex1.lock().unwrap(), LL + _ => mutex2.lock().unwrap(), LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:438:11 + --> tests/ui/significant_drop_in_scrutinee.rs:445:11 | LL | match if i > 1 { | ___________^ @@ -333,9 +342,9 @@ LL | | LL | | LL | | mutex1.lock().unwrap() ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -354,13 +363,12 @@ LL + } else { LL + mutex2.lock().unwrap() LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:494:11 + --> tests/ui/significant_drop_in_scrutinee.rs:501:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -374,25 +382,11 @@ LL | }; help: try moving the temporary above the match and create a copy | LL ~ let value = *s.lock().deref().deref(); -LL ~ match value { +LL ~ match (&value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:524:11 - | -LL | match s.lock().deref().deref() { - | ^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | matcher => println!("Value is {}", s.lock().deref()), - | -------- another value with significant `Drop` created here -LL | _ => println!("Value was not a match"), -LL | }; - | - temporary lives until here - | - = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:545:11 + --> tests/ui/significant_drop_in_scrutinee.rs:551:11 | LL | match mutex.lock().unwrap().i = i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -411,10 +405,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:553:11 + --> tests/ui/significant_drop_in_scrutinee.rs:559:15 | LL | match i = mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -425,12 +419,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i = mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i = value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:561:11 + --> tests/ui/significant_drop_in_scrutinee.rs:567:11 | LL | match mutex.lock().unwrap().i += 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -449,10 +443,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:569:11 + --> tests/ui/significant_drop_in_scrutinee.rs:575:16 | LL | match i += mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -463,12 +457,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i += mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i += value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:634:11 + --> tests/ui/significant_drop_in_scrutinee.rs:640:11 | LL | match rwlock.read().unwrap().to_number() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -477,20 +471,14 @@ LL | }; | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression - --> tests/ui/significant_drop_in_scrutinee.rs:646:14 +help: try moving the temporary above the match | -LL | for s in rwlock.read().unwrap().iter() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | } - | - temporary lives until here +LL ~ let value = rwlock.read().unwrap().to_number(); +LL ~ match value { | - = note: this might lead to deadlocks or other unexpected behavior error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:663:11 + --> tests/ui/significant_drop_in_scrutinee.rs:668:11 | LL | match mutex.lock().unwrap().foo() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -506,7 +494,7 @@ LL ~ match value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:726:11 + --> tests/ui/significant_drop_in_scrutinee.rs:731:11 | LL | match guard.take().len() { | ^^^^^^^^^^^^^^^^^^ @@ -521,5 +509,37 @@ LL ~ let value = guard.take().len(); LL ~ match value { | +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:757:16 + | +LL | for val in mutex.lock().unwrap().copy_old_lifetime() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().copy_old_lifetime(); +LL ~ for val in value { + | + +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:797:17 + | +LL | for val in [mutex.lock().unwrap()[0], 2] { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap()[0]; +LL ~ for val in [value, 2] { + | + error: aborting due to 27 previous errors From cfa150b0ddc28ea3723a3b4d0916e729dcea3246 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Wed, 22 May 2024 18:03:18 -0700 Subject: [PATCH 23/34] Rephrase and expand `empty_enum` documentation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove incorrect claim that “wrappers around it are the conventional way to define an uninhabited type”. * Discuss why one would use `!`, a newtype struct, or keep the enum. * Add links to relevant documentation. --- clippy_lints/src/empty_enum.rs | 45 +++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index 420888b6ccb35..d16714695cb7e 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -7,32 +7,53 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for `enum`s with no variants. + /// Checks for `enum`s with no variants, which therefore are uninhabited types + /// (cannot be instantiated). /// - /// As of this writing, the `never_type` is still a - /// nightly-only experimental API. Therefore, this lint is only triggered - /// if the `never_type` is enabled. + /// As of this writing, the `never_type` is still a nightly-only experimental API. + /// Therefore, this lint is only triggered if `#![feature(never_type)]` is enabled. /// /// ### Why is this bad? - /// If you want to introduce a type which - /// can't be instantiated, you should use `!` (the primitive type "never"), - /// or a wrapper around it, because `!` has more extensive - /// compiler support (type inference, etc...) and wrappers - /// around it are the conventional way to define an uninhabited type. - /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html) + /// * If you only want a type which can’t be instantiated, you should use [`!`] + /// (the primitive type "never"), because [`!`] has more extensive compiler support + /// (type inference, etc.) and implementations of common traits. /// + /// * If you need to introduce a distinct type, consider using a [newtype] `struct` + /// containing [`!`] instead (`struct MyType(pub !)`), because it is more idiomatic + /// to use a `struct` rather than an `enum` when an `enum` is unnecessary. + /// + /// If you do this, note that the [visibility] of the [`!`] field determines whether + /// the uninhabitedness is visible in documentation, and whether it can be pattern + /// matched to mark code unreachable. If the field is not visible, then the struct + /// acts like any other struct with private fields. + /// + /// * If the enum has no variants only because all variants happen to be + /// [disabled by conditional compilation][cfg], then it would be appropriate + /// to allow the lint, with `#[allow(empty_enum)]`. + /// + /// For further information, visit + /// [the never type’s documentation][`!`]. /// /// ### Example /// ```no_run - /// enum Test {} + /// enum CannotExist {} /// ``` /// /// Use instead: /// ```no_run /// #![feature(never_type)] /// - /// struct Test(!); + /// /// Use the `!` type directly... + /// type CannotExist = !; + /// + /// /// ...or define a newtype which is distinct. + /// struct CannotExist2(pub !); /// ``` + /// + /// [`!`]: https://doc.rust-lang.org/std/primitive.never.html + /// [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html + /// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction + /// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html #[clippy::version = "pre 1.29.0"] pub EMPTY_ENUM, pedantic, From 0c653d9f91ad8211701ce685b6cb2a2df1253df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 22 May 2024 16:46:05 +0200 Subject: [PATCH 24/34] Remove `LintDiagnostic::msg` * instead simply set the primary message inside the lint decorator functions * it used to be this way before [#]101986 which introduced `msg` to prevent good path delayed bugs (which no longer exist) from firing under certain circumstances when lints were suppressed / silenced * this is no longer necessary for various reasons I presume * it shaves off complexity and makes further changes easier to implement --- clippy_utils/src/diagnostics.rs | 18 ++++++++++++------ tests/ui-internal/disallow_span_lint.rs | 4 ++-- tests/ui-internal/disallow_span_lint.stderr | 8 ++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index dc0a139e3c786..0641d37cd9a67 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -61,7 +61,8 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { /// ``` pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into, msg: impl Into) { #[expect(clippy::disallowed_methods)] - cx.span_lint(lint, sp, msg.into(), |diag| { + cx.span_lint(lint, sp, |diag| { + diag.primary_message(msg); docs_link(diag, lint); }); } @@ -109,7 +110,8 @@ pub fn span_lint_and_help( help: impl Into, ) { #[expect(clippy::disallowed_methods)] - cx.span_lint(lint, span, msg.into(), |diag| { + cx.span_lint(lint, span, |diag| { + diag.primary_message(msg); if let Some(help_span) = help_span { diag.span_help(help_span, help.into()); } else { @@ -165,7 +167,8 @@ pub fn span_lint_and_note( note: impl Into, ) { #[expect(clippy::disallowed_methods)] - cx.span_lint(lint, span, msg.into(), |diag| { + cx.span_lint(lint, span, |diag| { + diag.primary_message(msg); if let Some(note_span) = note_span { diag.span_note(note_span, note.into()); } else { @@ -201,7 +204,8 @@ where F: FnOnce(&mut Diag<'_, ()>), { #[expect(clippy::disallowed_methods)] - cx.span_lint(lint, sp, msg, |diag| { + cx.span_lint(lint, sp, |diag| { + diag.primary_message(msg); f(diag); docs_link(diag, lint); }); @@ -233,7 +237,8 @@ where /// the `#[allow]` will work. pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, sp: Span, msg: impl Into) { #[expect(clippy::disallowed_methods)] - cx.tcx.node_span_lint(lint, hir_id, sp, msg.into(), |diag| { + cx.tcx.node_span_lint(lint, hir_id, sp, |diag| { + diag.primary_message(msg); docs_link(diag, lint); }); } @@ -271,7 +276,8 @@ pub fn span_lint_hir_and_then( f: impl FnOnce(&mut Diag<'_, ()>), ) { #[expect(clippy::disallowed_methods)] - cx.tcx.node_span_lint(lint, hir_id, sp, msg.into(), |diag| { + cx.tcx.node_span_lint(lint, hir_id, sp, |diag| { + diag.primary_message(msg); f(diag); docs_link(diag, lint); }); diff --git a/tests/ui-internal/disallow_span_lint.rs b/tests/ui-internal/disallow_span_lint.rs index 5a2a868ed3ec6..b91a83308b5f5 100644 --- a/tests/ui-internal/disallow_span_lint.rs +++ b/tests/ui-internal/disallow_span_lint.rs @@ -11,11 +11,11 @@ use rustc_lint::{Lint, LintContext}; use rustc_middle::ty::TyCtxt; pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into, msg: impl Into) { - cx.span_lint(lint, span, msg, |_| {}); + cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); } pub fn b(tcx: TyCtxt<'_>, lint: &'static Lint, hir_id: HirId, span: impl Into, msg: impl Into) { - tcx.node_span_lint(lint, hir_id, span, msg, |_| {}); + tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); } fn main() {} diff --git a/tests/ui-internal/disallow_span_lint.stderr b/tests/ui-internal/disallow_span_lint.stderr index cfc590bed3697..1cfbc8efc8ed1 100644 --- a/tests/ui-internal/disallow_span_lint.stderr +++ b/tests/ui-internal/disallow_span_lint.stderr @@ -1,8 +1,8 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` --> tests/ui-internal/disallow_span_lint.rs:14:5 | -LL | cx.span_lint(lint, span, msg, |_| {}); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml) = note: `-D clippy::disallowed-methods` implied by `-D warnings` @@ -11,8 +11,8 @@ LL | cx.span_lint(lint, span, msg, |_| {}); error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` --> tests/ui-internal/disallow_span_lint.rs:18:5 | -LL | tcx.node_span_lint(lint, hir_id, span, msg, |_| {}); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml) From 0f5338cd903c715d411dee6af95f076177914e42 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Wed, 22 May 2024 22:21:01 -0700 Subject: [PATCH 25/34] =?UTF-8?q?For=20restriction=20lints,=20replace=20?= =?UTF-8?q?=E2=80=9CWhy=20is=20this=20bad=3F=E2=80=9D=20with=20=E2=80=9CWh?= =?UTF-8?q?y=20restrict=20this=3F=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `restriction` group contains many lints which are not about necessarily “bad” things, but style choices — perhaps even style choices which contradict conventional Rust style — or are otherwise very situational. This results in silly wording like “Why is this bad? It isn't, but ...”, which I’ve seen confuse a newcomer at least once. To improve this situation, this commit replaces the “Why is this bad?” section heading with “Why restrict this?”, for most, but not all, restriction lints. I left alone the ones whose placement in the restriction group is more incidental. In order to make this make sense, I had to remove the “It isn't, but” texts from the contents of the sections. Sometimes further changes were needed, or there were obvious fixes to make, and I went ahead and made those changes without attempting to split them into another commit, even though many of them are not strictly necessary for the “Why restrict this?” project. --- book/src/development/adding_lints.md | 5 ++++ clippy_dev/src/new_lint.rs | 7 ++++- clippy_lints/src/absolute_paths.rs | 2 +- clippy_lints/src/allow_attributes.rs | 7 ++--- clippy_lints/src/as_conversions.rs | 2 +- clippy_lints/src/asm_syntax.rs | 10 +++---- .../src/assertions_on_result_states.rs | 20 ++++++++++---- clippy_lints/src/attrs/mod.rs | 6 ++--- clippy_lints/src/casts/mod.rs | 6 ++--- clippy_lints/src/create_dir.rs | 6 +++-- clippy_lints/src/dbg_macro.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 5 ++-- .../src/default_union_representation.rs | 2 +- clippy_lints/src/disallowed_script_idents.rs | 4 +-- clippy_lints/src/doc/mod.rs | 2 +- clippy_lints/src/drop_forget_ref.rs | 7 ++--- clippy_lints/src/else_if_without_else.rs | 2 +- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/empty_with_brackets.rs | 24 +++++++++++++---- clippy_lints/src/endian_bytes.rs | 15 +++++------ clippy_lints/src/error_impl_error.rs | 2 +- clippy_lints/src/exhaustive_items.rs | 16 ++++++------ clippy_lints/src/exit.rs | 12 +++++---- clippy_lints/src/float_literal.rs | 6 ++--- clippy_lints/src/format_push_string.rs | 2 +- clippy_lints/src/functions/mod.rs | 11 ++++---- clippy_lints/src/if_then_some_else_none.rs | 2 +- clippy_lints/src/implicit_return.rs | 13 +++++----- clippy_lints/src/indexing_slicing.rs | 7 ++--- clippy_lints/src/inherent_impl.rs | 2 +- .../src/integer_division_remainder_used.rs | 6 ++--- clippy_lints/src/iter_over_hash_type.rs | 4 +-- clippy_lints/src/large_include_file.rs | 8 +++--- clippy_lints/src/let_underscore.rs | 9 +++---- clippy_lints/src/literal_representation.rs | 4 +-- clippy_lints/src/loops/mod.rs | 6 ++--- clippy_lints/src/matches/mod.rs | 6 ++--- clippy_lints/src/methods/mod.rs | 21 ++++++++------- clippy_lints/src/min_ident_chars.rs | 7 +++-- clippy_lints/src/misc_early/mod.rs | 6 ++--- clippy_lints/src/missing_assert_message.rs | 2 +- .../src/missing_asserts_for_indexing.rs | 2 +- clippy_lints/src/missing_doc.rs | 4 +-- clippy_lints/src/missing_inline.rs | 19 +++++++++----- clippy_lints/src/missing_trait_methods.rs | 10 +++---- .../src/mixed_read_write_in_expression.rs | 9 ++++--- clippy_lints/src/module_style.rs | 6 ++--- .../src/multiple_unsafe_ops_per_block.rs | 2 +- clippy_lints/src/mutex_atomic.rs | 2 +- clippy_lints/src/operators/mod.rs | 12 ++++----- clippy_lints/src/panic_in_result_fn.rs | 4 +-- clippy_lints/src/panic_unimplemented.rs | 18 ++++++------- clippy_lints/src/partial_pub_fields.rs | 9 ++++--- clippy_lints/src/pattern_type_mismatch.rs | 5 ++-- clippy_lints/src/pub_use.rs | 8 +++--- clippy_lints/src/question_mark_used.rs | 2 +- clippy_lints/src/raw_strings.rs | 6 +++-- clippy_lints/src/redundant_slicing.rs | 2 +- .../src/redundant_type_annotations.rs | 2 +- clippy_lints/src/ref_patterns.rs | 4 ++- clippy_lints/src/same_name_method.rs | 2 +- clippy_lints/src/semicolon_block.rs | 6 ++--- clippy_lints/src/shadow.rs | 26 +++++++++++-------- clippy_lints/src/single_call_fn.rs | 19 +++++++++----- .../src/single_char_lifetime_names.rs | 5 ++-- clippy_lints/src/std_instead_of_core.rs | 12 +++------ clippy_lints/src/strings.rs | 19 +++++++------- .../src/suspicious_xor_used_as_pow.rs | 4 ++- clippy_lints/src/tests_outside_test_module.rs | 4 ++- clippy_lints/src/types/mod.rs | 4 +-- .../src/undocumented_unsafe_blocks.rs | 9 +++---- clippy_lints/src/unicode.rs | 2 +- clippy_lints/src/unnecessary_self_imports.rs | 2 +- clippy_lints/src/unwrap_in_result.rs | 6 +++-- clippy_lints/src/visibility.rs | 4 +-- clippy_lints/src/write.rs | 13 ++++++---- 76 files changed, 306 insertions(+), 245 deletions(-) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 415022612caaa..48c00bcbf3413 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -587,6 +587,11 @@ declare_clippy_lint! { } ``` +If the lint is in the `restriction` group because it lints things that are not +necessarily “bad” but are more of a style choice, then replace the +“Why is this bad?” section heading with “Why restrict this?”, to avoid writing +“Why is this bad? It isn't, but ...”. + Once your lint is merged, this documentation will show up in the [lint list][lint_list]. diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index b6481dde4dde0..2e56eb8ec15f8 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -331,12 +331,17 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { } fn get_lint_declaration(name_upper: &str, category: &str) -> String { + let justification_heading = if category == "restriction" { + "Why restrict this?" + } else { + "Why is this bad?" + }; formatdoc!( r#" declare_clippy_lint! {{ /// ### What it does /// - /// ### Why is this bad? + /// ### {justification_heading} /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 8ba661afeeb6f..461117cf965dc 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of items through absolute paths, like `std::env::current_dir`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Many codebases have their own style when it comes to importing, but one that is seldom used /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you /// should add a `use` statement. diff --git a/clippy_lints/src/allow_attributes.rs b/clippy_lints/src/allow_attributes.rs index 39fc49dee377d..123d0e51eeee2 100644 --- a/clippy_lints/src/allow_attributes.rs +++ b/clippy_lints/src/allow_attributes.rs @@ -19,10 +19,11 @@ declare_clippy_lint! { /// This lint only warns outer attributes (`#[allow]`), as inner attributes /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// - /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// ### Why restrict this? + /// `#[allow]` attributes can linger after their reason for existence is gone. + /// `#[expect]` attributes suppress the lint emission, but emit a warning if /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. + /// lint is no longer triggered, which may indicate the attribute can be removed. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index e3daf75c3eb6c..cfa25005a05ed 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// There is a good explanation the reason why this lint should work in this way and how it is useful /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). /// - /// ### Why is this bad? + /// ### Why restrict this? /// `as` conversions will perform many kinds of /// conversions, including silently lossy conversions and dangerous coercions. /// There are cases when it makes sense to use `as`, so the lint is diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 7c88bfc97ca49..0db1456d40bfc 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -65,9 +65,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of Intel x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for AT&T x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of AT&T x86 assembly syntax. /// /// ### Example /// @@ -114,9 +113,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of AT&T x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for Intel x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of Intel x86 assembly syntax. /// /// ### Example /// diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index aec22965b1b0b..7217686dcca5b 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -16,23 +16,33 @@ declare_clippy_lint! { /// ### What it does /// Checks for `assert!(r.is_ok())` or `assert!(r.is_err())` calls. /// - /// ### Why is this bad? - /// An assertion failure cannot output an useful message of the error. + /// ### Why restrict this? + /// This form of assertion does not show any of the information present in the `Result` + /// other than which variant it isn’t. /// /// ### Known problems /// The suggested replacement decreases the readability of code and log output. /// /// ### Example - /// ```rust,ignore + /// ```rust,no_run /// # let r = Ok::<_, ()>(()); /// assert!(r.is_ok()); - /// # let r = Err::<_, ()>(()); + /// # let r = Err::<(), _>(()); /// assert!(r.is_err()); /// ``` + /// + /// Use instead: + /// + /// ```rust,no_run + /// # let r = Ok::<_, ()>(()); + /// r.unwrap(); + /// # let r = Err::<(), _>(()); + /// r.unwrap_err(); + /// ``` #[clippy::version = "1.64.0"] pub ASSERTIONS_ON_RESULT_STATES, restriction, - "`assert!(r.is_ok())`/`assert!(r.is_err())` gives worse error message than directly calling `r.unwrap()`/`r.unwrap_err()`" + "`assert!(r.is_ok())` or `assert!(r.is_err())` gives worse panic messages than directly calling `r.unwrap()` or `r.unwrap_err()`" } declare_lint_pass!(AssertionsOnResultStates => [ASSERTIONS_ON_RESULT_STATES]); diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 39f4060779957..a24bd5ed44be4 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -309,9 +309,9 @@ declare_clippy_lint! { /// /// (This requires the `lint_reasons` feature) /// - /// ### Why is this bad? - /// Allowing a lint should always have a reason. This reason should be documented to - /// ensure that others understand the reasoning + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index bd2c96f01f6f4..e60c36ced75da 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -303,7 +303,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to any integer type. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Casting a function pointer to an integer can have surprising results and can occur /// accidentally if parentheses are omitted from a function call. If you aren't doing anything /// low-level with function pointers then you can opt-out of casting functions to integers in @@ -535,8 +535,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of `as _` conversion using inferred type. /// - /// ### Why is this bad? - /// The conversion might include lossy conversion and dangerous cast that might go + /// ### Why restrict this? + /// The conversion might include lossy conversion or a dangerous cast that might go /// undetected due to the type being inferred. /// /// The lint is allowed by default as using `_` is less wordy than always specifying the type. diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 7a3d5a0709127..27c00948a8f2b 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -10,8 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead. /// - /// ### Why is this bad? - /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. + /// ### Why restrict this? + /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`, + /// resulting in failure when more than one directory needs to be created or when the directory already exists. + /// Crates which never need to specifically create a single directory may wish to prevent this mistake. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index db5937266047d..b0590b0a71cb2 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of the [`dbg!`](https://doc.rust-lang.org/std/macro.dbg.html) macro. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `dbg!` macro is intended as a debugging tool. It should not be present in released /// software or committed to a version control system. /// diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 1d6c4ce72e18b..fbc4ede37b1c4 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -22,9 +22,8 @@ declare_clippy_lint! { /// /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. /// - /// ### Why is this bad? - /// For those who are very careful about types, default numeric fallback - /// can be a pitfall that cause unexpected runtime behavior. + /// ### Why restrict this? + /// To ensure that every numeric type is chosen explicitly rather than implicitly. /// /// ### Known problems /// This lint can only be allowed at the function level or above. diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index b4290b6437f27..3fa9bad0d03da 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Unions in Rust have unspecified layout by default, despite many people thinking that they /// lay out each field at the start of the union (like C does). That is, there are no guarantees /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index def4b5932b4b1..a995f06fb73dc 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -20,11 +20,11 @@ declare_clippy_lint! { /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html /// - /// ### Why is this bad? + /// ### Why restrict this? /// It may be not desired to have many different scripts for /// identifiers in the codebase. /// - /// Note that if you only want to allow plain English, you might want to use + /// Note that if you only want to allow typical English, you might want to use /// built-in [`non_ascii_idents`] lint instead. /// /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 81c47d07c3563..e9c4559ff4d2c 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -261,7 +261,7 @@ declare_clippy_lint! { /// Checks for the doc comments of publicly visible /// safe functions and traits and warns if there is a `# Safety` section. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe functions and traits are safe to implement and therefore do not /// need to describe safety preconditions that users are required to uphold. /// diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 119473c2454be..4a6ffcd9a7888 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -52,9 +52,10 @@ declare_clippy_lint! { /// Checks for usage of `std::mem::forget(t)` where `t` is /// `Drop` or has a field that implements `Drop`. /// - /// ### Why is this bad? - /// `std::mem::forget(t)` prevents `t` from running its - /// destructor, possibly causing leaks. + /// ### Why restrict this? + /// `std::mem::forget(t)` prevents `t` from running its destructor, possibly causing leaks. + /// It is not possible to detect all means of creating leaks, but it may be desirable to + /// prohibit the simple ones. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index a6ca7fe9e0bb3..bb6f9aac22363 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Checks for usage of if expressions with an `else if` branch, /// but without a final `else` branch. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10). /// /// ### Example diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index 74db250b3ae92..c5fc72b5e2d8e 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for empty `Drop` implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Empty `Drop` implementations have no effect when dropping an instance of the type. They are /// most likely useless. However, an empty `Drop` implementation prevents a type from being /// destructured, which might be the intention behind adding the implementation as a marker. diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 969df6d85b5a0..745599b0e57a2 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -11,16 +11,23 @@ declare_clippy_lint! { /// ### What it does /// Finds structs without fields (a so-called "empty struct") that are declared with brackets. /// - /// ### Why is this bad? - /// Empty brackets after a struct declaration can be omitted. + /// ### Why restrict this? + /// Empty brackets after a struct declaration can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the struct, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// struct Cookie {} + /// struct Biscuit(); /// ``` /// Use instead: /// ```no_run /// struct Cookie; + /// struct Biscuit; /// ``` #[clippy::version = "1.62.0"] pub EMPTY_STRUCTS_WITH_BRACKETS, @@ -32,14 +39,20 @@ declare_clippy_lint! { /// ### What it does /// Finds enum variants without fields that are declared with empty brackets. /// - /// ### Why is this bad? - /// Empty brackets while defining enum variants are redundant and can be omitted. + /// ### Why restrict this? + /// Empty brackets after a enum variant declaration are redundant and can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the variant, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// enum MyEnum { /// HasData(u8), - /// HasNoData(), // redundant parentheses + /// HasNoData(), // redundant parentheses + /// NoneHereEither {}, // redundant braces /// } /// ``` /// @@ -48,6 +61,7 @@ declare_clippy_lint! { /// enum MyEnum { /// HasData(u8), /// HasNoData, + /// NoneHereEither, /// } /// ``` #[clippy::version = "1.77.0"] diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index dd03df797de3f..bb766e9633872 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -13,8 +13,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_ne_bytes` method and/or the function `from_ne_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may prefer to specify the target endianness explicitly. + /// ### Why restrict this? + /// To ensure use of explicitly chosen endianness rather than the target’s endianness, + /// such as when implementing network protocols or file formats rather than FFI. /// /// ### Example /// ```rust,ignore @@ -31,9 +32,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or big endian. + /// ### Why restrict this? + /// To ensure use of big endian or the target’s endianness rather than little endian. /// /// ### Example /// ```rust,ignore @@ -50,9 +50,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or little endian. + /// ### Why restrict this? + /// To ensure use of little endian or the target’s endianness rather than big endian. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs index 8dbb47fadc5da..8e49138cd26ba 100644 --- a/clippy_lints/src/error_impl_error.rs +++ b/clippy_lints/src/error_impl_error.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for types named `Error` that implement `Error`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// It can become confusing when a codebase has 20 types all named `Error`, requiring either /// aliasing them in the `use` statement or qualifying them like `my_module::Error`. This /// hinders comprehension, as it requires you to memorize every variation of importing `Error` diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 9ffda64574243..436dc8611bdeb 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -10,10 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive enums are typically fine, but a project which does - /// not wish to make a stability commitment around exported enums may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making an `enum` exhaustive is a stability commitment: adding a variant is a breaking change. + /// A project may wish to ensure that there are no exhaustive enums or that every exhaustive + /// `enum` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run @@ -40,10 +40,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `struct`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive structs are typically fine, but a project which does - /// not wish to make a stability commitment around exported structs may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making a `struct` exhaustive is a stability commitment: adding a field is a breaking change. + /// A project may wish to ensure that there are no exhaustive structs or that every exhaustive + /// `struct` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 106844dd43489..91c94d66458dc 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -9,11 +9,13 @@ declare_clippy_lint! { /// ### What it does /// Detects calls to the `exit()` function which terminates the program. /// - /// ### Why is this bad? - /// Exit terminates the program at the location it is called. For unrecoverable - /// errors `panics` should be used to provide a stacktrace and potentially other - /// information. A normal termination or one with an error code should happen in - /// the main function. + /// ### Why restrict this? + /// `exit()` immediately terminates the program with no information other than an exit code. + /// This provides no means to troubleshoot a problem, and may be an unexpected side effect. + /// + /// Codebases may use this lint to require that all exits are performed either by panicking + /// (which produces a message, a code location, and optionally a backtrace) + /// or by returning from `main()` (which is a single place to look). /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 2cd4e9e99a56b..4ec9bd757ff45 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -38,9 +38,9 @@ declare_clippy_lint! { /// Checks for whole number float literals that /// cannot be represented as the underlying type without loss. /// - /// ### Why is this bad? - /// Rust will silently lose precision during - /// conversion to a float. + /// ### Why restrict this? + /// If the value was intended to be exact, it will not be. + /// This may be especially surprising when the lost precision is to the left of the decimal point. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 2b08437d82711..a75538dd329b5 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Detects cases where the result of a `format!` call is /// appended to an existing `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Introduces an extra, avoidable heap allocation. /// /// ### Known problems diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index dfcaac9abefc7..26534492dddd7 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -338,8 +338,10 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Lints when `impl Trait` is being used in a function's parameters. - /// ### Why is this bad? - /// Turbofish syntax (`::<>`) cannot be used when `impl Trait` is being used, making `impl Trait` less powerful. Readability may also be a factor. + /// + /// ### Why restrict this? + /// Turbofish syntax (`::<>`) cannot be used to specify the type of an `impl Trait` parameter, + /// making `impl Trait` less powerful. Readability may also be a factor. /// /// ### Example /// ```no_run @@ -366,9 +368,8 @@ declare_clippy_lint! { /// Lints when the name of function parameters from trait impl is /// different than its default implementation. /// - /// ### Why is this bad? - /// Using the default name for parameters of a trait method is often - /// more desirable for consistency's sake. + /// ### Why restrict this? + /// Using the default name for parameters of a trait method is more consistent. /// /// ### Example /// ```rust diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index f5ba62ae432e8..0b20081521987 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated /// in comparison to `bool::then`. diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 5288efd8df8cd..cc342007ec617 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -16,12 +16,13 @@ declare_clippy_lint! { /// ### What it does /// Checks for missing return statements at the end of a block. /// - /// ### Why is this bad? - /// Actually omitting the return keyword is idiomatic Rust code. Programmers - /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss - /// the last returning statement because the only difference is a missing `;`. Especially in bigger - /// code with multiple return paths having a `return` keyword makes it easier to find the - /// corresponding statements. + /// ### Why restrict this? + /// Omitting the return keyword whenever possible is idiomatic Rust code, but: + /// + /// * Programmers coming from other languages might prefer the expressiveness of `return`. + /// * It's possible to miss the last returning statement because the only difference is a missing `;`. + /// * Especially in bigger code with multiple return paths, having a `return` keyword makes it easier to find the + /// corresponding statements. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 35fcd8cdd3547..e3e79749bea60 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -45,9 +45,10 @@ declare_clippy_lint! { /// does report on arrays if we can tell that slicing operations are in bounds and does not /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint. /// - /// ### Why is this bad? - /// Indexing and slicing can panic at runtime and there are - /// safe alternatives. + /// ### Why restrict this? + /// To avoid implicit panics from indexing and slicing. + /// There are “checked” alternatives which do not panic, and can be used with `unwrap()` to make + /// an explicit panic when it is desired. /// /// ### Example /// ```rust,no_run diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 1127f00abde04..95ae591884bc4 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for multiple inherent implementations of a struct /// - /// ### Why is this bad? + /// ### Why restrict this? /// Splitting the implementation of a type makes the code harder to navigate. /// /// ### Example diff --git a/clippy_lints/src/integer_division_remainder_used.rs b/clippy_lints/src/integer_division_remainder_used.rs index a3577b765c03f..cf598d5045ec3 100644 --- a/clippy_lints/src/integer_division_remainder_used.rs +++ b/clippy_lints/src/integer_division_remainder_used.rs @@ -7,10 +7,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for the usage of division (/) and remainder (%) operations - /// when performed on any integer types using the default Div and Rem trait implementations. + /// Checks for the usage of division (`/`) and remainder (`%`) operations + /// when performed on any integer types using the default `Div` and `Rem` trait implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). /// diff --git a/clippy_lints/src/iter_over_hash_type.rs b/clippy_lints/src/iter_over_hash_type.rs index 6c6eff9ba48bd..fb29d98241797 100644 --- a/clippy_lints/src/iter_over_hash_type.rs +++ b/clippy_lints/src/iter_over_hash_type.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops. /// - /// ### Why is this bad? - /// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in + /// ### Why restrict this? + /// Because hash types are unordered, when iterated through such as in a `for` loop, the values are returned in /// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies. /// In addition, the unknown order of the elements may reduce readability or introduce other undesired /// side effects. diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 790bed580fd11..07efee159aab4 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -10,10 +10,12 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does /// Checks for the inclusion of large files via `include_bytes!()` - /// and `include_str!()` + /// or `include_str!()`. /// - /// ### Why is this bad? - /// Including large files can increase the size of the binary + /// ### Why restrict this? + /// Including large files can undesirably increase the size of the binary produced by the compiler. + /// This lint may be used to catch mistakes where an unexpectedly large file is included, or + /// temporarily to obtain a list of all large files. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 619e933b4fff4..9fd4f509aa473 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -12,9 +12,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for `let _ = ` where expr is `#[must_use]` /// - /// ### Why is this bad? - /// It's better to explicitly handle the value of a `#[must_use]` - /// expr + /// ### Why restrict this? + /// To ensure that all `#[must_use]` types are used rather than ignored. /// /// ### Example /// ```no_run @@ -96,8 +95,8 @@ declare_clippy_lint! { /// Checks for `let _ = ` without a type annotation, and suggests to either provide one, /// or remove the `let` keyword altogether. /// - /// ### Why is this bad? - /// The `let _ = ` expression ignores the value of `` but will remain doing so even + /// ### Why restrict this? + /// The `let _ = ` expression ignores the value of ``, but will continue to do so even /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. /// diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 2348dd18220fe..d2a140a36a83f 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -132,8 +132,8 @@ declare_clippy_lint! { /// ### What it does /// Warns if there is a better representation for a numeric literal. /// - /// ### Why is this bad? - /// Especially for big powers of 2 a hexadecimal representation is more + /// ### Why restrict this? + /// Especially for big powers of 2, a hexadecimal representation is usually more /// readable than a decimal representation. /// /// ### Example diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 3dcb050d77e68..0868294215305 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -675,9 +675,9 @@ declare_clippy_lint! { /// Checks for infinite loops in a function where the return type is not `!` /// and lint accordingly. /// - /// ### Why is this bad? - /// A loop should be gently exited somewhere, or at least mark its parent function as - /// never return (`!`). + /// ### Why restrict this? + /// Making the return type `!` serves as documentation that the function does not return. + /// If the function is not intended to loop infinitely, then this lint may detect a bug. /// /// ### Example /// ```no_run,ignore diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index ee9f48d71ad80..691ecd57535ac 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -260,7 +260,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for wildcard enum matches using `_`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// New enum variants added by library updates can be missed. /// /// ### Known problems @@ -435,7 +435,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Correctness and readability. It's like having a wildcard pattern after /// matching all enum variants explicitly. /// @@ -861,7 +861,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Err(x)?`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `?` operator is designed to allow calls that /// can fail to be easily chained. For example, `foo()?.bar()` or /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 60666445d08bc..13c5f537ca409 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -257,7 +257,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// It is better to handle the `None` or `Err` case, /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is @@ -333,7 +333,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Usually it is better to handle the `None` or `Err` case. /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why /// this lint is `Allow` by default. @@ -1029,8 +1029,8 @@ declare_clippy_lint! { /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified /// function syntax instead (e.g., `Rc::clone(foo)`). /// - /// ### Why is this bad? - /// Calling '.clone()' on an Rc, Arc, or Weak + /// ### Why restrict this? + /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` /// can obscure the fact that only the pointer is being cloned, not the underlying /// data. /// @@ -1051,7 +1051,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub CLONE_ON_REF_PTR, restriction, - "using 'clone' on a ref-counted pointer" + "using `clone` on a ref-counted pointer" } declare_clippy_lint! { @@ -1359,7 +1359,7 @@ declare_clippy_lint! { /// Checks for usage of `.get().unwrap()` (or /// `.get_mut().unwrap`) on a standard library type which implements `Index` /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using the Index trait (`[]`) is more clear and more /// concise. /// @@ -1743,7 +1743,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `FileType::is_file()`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// When people testing a file type with `FileType::is_file` /// they are testing whether a path is something they can get bytes from. But /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover @@ -2688,8 +2688,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for instances of `map_err(|_| Some::Enum)` /// - /// ### Why is this bad? - /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// ### Why restrict this? + /// This `map_err` throws away the original error rather than allowing the enum to + /// contain and report the cause of the error. /// /// ### Example /// Before: @@ -3145,7 +3146,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of File::read_to_end and File::read_to_string. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) /// diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index c6b7f5b0ce277..e43b712021ae4 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -12,14 +12,13 @@ use std::borrow::Cow; declare_clippy_lint! { /// ### What it does - /// Checks for idents which comprise of a single letter. + /// Checks for identifiers which consist of a single character (or fewer than the configured threshold). /// /// Note: This lint can be very noisy when enabled; it may be desirable to only enable it /// temporarily. /// - /// ### Why is this bad? - /// In many cases it's not, but at times it can severely hinder readability. Some codebases may - /// wish to disallow this to improve readability. + /// ### Why restrict this? + /// To improve readability by requiring that every variable has a name more specific than a single letter can be. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 2f5499d7656fd..fedcfd11fdccc 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -23,7 +23,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for structure field patterns bound to wildcards. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using `..` instead is shorter and leaves the focus on /// the fields that are actually bound. /// @@ -138,7 +138,7 @@ declare_clippy_lint! { /// To enforce unseparated literal suffix style, /// see the `separated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example @@ -166,7 +166,7 @@ declare_clippy_lint! { /// To enforce separated literal suffix style, /// see the `unseparated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example diff --git a/clippy_lints/src/missing_assert_message.rs b/clippy_lints/src/missing_assert_message.rs index 04df7b7a7e5a4..dd98352da8601 100644 --- a/clippy_lints/src/missing_assert_message.rs +++ b/clippy_lints/src/missing_assert_message.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Checks assertions without a custom panic message. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Without a good custom message, it'd be hard to understand what went wrong when the assertion fails. /// A good custom message should be more about why the failure of the assertion is problematic /// and not what is failed because the assertion already conveys that. diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index c29e46b941c60..752723a0c68ee 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// Checks for repeated slice indexing without asserting beforehand that the length /// is greater than the largest index used to index into the slice. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In the general case where the compiler does not have a lot of information /// about the length of a slice, indexing it repeatedly will generate a bounds check /// for every single index. diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 2fb784dae1cd4..ca344dc5c8109 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -20,9 +20,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Warns if there is missing doc for any private documentable item + /// Warns if there is missing documentation for any private documentable item. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Doc is good. *rustc* has a `MISSING_DOCS` /// allowed-by-default lint for /// public members, but has no way to enforce documentation of private items. diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index c6a76478806ac..33a14d8b7fed3 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -10,13 +10,16 @@ declare_clippy_lint! { /// It lints if an exported function, method, trait method with default impl, /// or trait method impl is not `#[inline]`. /// - /// ### Why is this bad? - /// In general, it is not. Functions can be inlined across - /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, - /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates - /// might intend for most of the methods in their public API to be able to be inlined across - /// crates even when LTO is disabled. For these types of crates, enabling this lint might make - /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and + /// ### Why restrict this? + /// When a function is not marked `#[inline]`, it is not + /// [a “small” candidate for automatic inlining][small], and LTO is not in use, then it is not + /// possible for the function to be inlined into the code of any crate other than the one in + /// which it is defined. Depending on the role of the function and the relationship of the crates, + /// this could significantly reduce performance. + /// + /// Certain types of crates might intend for most of the methods in their public API to be able + /// to be inlined across crates even when LTO is disabled. + /// This lint allows those crates to require all exported methods to be `#[inline]` by default, and /// then opt out for specific methods where this might not make sense. /// /// ### Example @@ -51,6 +54,8 @@ declare_clippy_lint! { /// fn def_bar() {} // missing #[inline] /// } /// ``` + /// + /// [small]: https://github.com/rust-lang/rust/pull/116505 #[clippy::version = "pre 1.29.0"] pub MISSING_INLINE_IN_PUBLIC_ITEMS, restriction, diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index 6f844bc646a24..85029a5e6a0d4 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -10,16 +10,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks if a provided method is used implicitly by a trait - /// implementation. A usage example would be a wrapper where every method - /// should perform some operation before delegating to the inner type's /// implementation. /// + /// ### Why restrict this? + /// To ensure that a certain implementation implements every method; for example, + /// a wrapper type where every method should delegate to the corresponding method of + /// the inner type's implementation. + /// /// This lint should typically be enabled on a specific trait `impl` item /// rather than globally. /// - /// ### Why is this bad? - /// Indicates that a method is missing. - /// /// ### Example /// ```no_run /// trait Trait { diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 181351910db62..9c5a8a0cfcdfa 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -12,9 +12,10 @@ declare_clippy_lint! { /// whether the read occurs before or after the write depends on the evaluation /// order of sub-expressions. /// - /// ### Why is this bad? - /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands), - /// the operands of these expressions are evaluated before applying the effects of the expression. + /// ### Why restrict this? + /// While [the evaluation order of sub-expressions] is fully specified in Rust, + /// it still may be confusing to read an expression where the evaluation order + /// affects its behavior. /// /// ### Known problems /// Code which intentionally depends on the evaluation @@ -40,6 +41,8 @@ declare_clippy_lint! { /// }; /// let a = tmp + x; /// ``` + /// + /// [order]: (https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands) #[clippy::version = "pre 1.29.0"] pub MIXED_READ_WRITE_IN_EXPRESSION, restriction, diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 6c031c081750a..305499f9da43c 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -10,9 +10,9 @@ use std::path::{Component, Path}; declare_clippy_lint! { /// ### What it does - /// Checks that module layout uses only self named module files, bans `mod.rs` files. + /// Checks that module layout uses only self named module files; bans `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example @@ -41,7 +41,7 @@ declare_clippy_lint! { /// ### What it does /// Checks that module layout uses only `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 0e13806678059..6830d48cfd8c5 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `unsafe` blocks that contain more than one unsafe operation. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Combined with `undocumented_unsafe_blocks`, /// this lint ensures that each unsafe operation must be independently justified. /// Combined with `unused_unsafe`, this lint also ensures diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 7ecc86176942b..853e476a006c6 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Mutex` where an atomic will do. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using a mutex just to make access to a plain bool or /// reference sequential is shooting flies with cannons. /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index e002429e3a47a..0948973df5a35 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -70,7 +70,7 @@ declare_clippy_lint! { /// Known safe built-in types like `Wrapping` or `Saturating`, floats, operations in constant /// environments, allowed types and non-constant operations that won't overflow are ignored. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For integers, overflow will trigger a panic in debug builds or wrap the result in /// release mode; division by zero will cause a panic in either mode. As a result, it is /// desirable to explicitly call checked, wrapping or saturating arithmetic methods. @@ -100,7 +100,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for float arithmetic. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some embedded systems or kernel development, it /// can be useful to rule out floating-point numbers. /// @@ -502,7 +502,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for division of integers /// - /// ### Why is this bad? + /// ### Why restrict this? /// When outside of some very specific algorithms, /// integer division is very often a mistake because it discards the /// remainder. @@ -596,7 +596,7 @@ declare_clippy_lint! { /// value and constant, except in functions called `*eq*` (which probably /// implement equality for a type involving floats). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Floating point calculations are usually imprecise, so /// asking if two values are *exactly* equal is asking for trouble. For a good /// guide on what to do, see [the floating point @@ -653,8 +653,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for modulo arithmetic. /// - /// ### Why is this bad? - /// The results of modulo (%) operation might differ + /// ### Why restrict this? + /// The results of modulo (`%`) operation might differ /// depending on the language, when negative numbers are involved. /// If you interop with different languages it might be beneficial /// to double check all places that use modulo arithmetic. diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs index f821a4efee7b2..806638c0505ba 100644 --- a/clippy_lints/src/panic_in_result_fn.rs +++ b/clippy_lints/src/panic_in_result_fn.rs @@ -13,9 +13,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Checks for usage of `panic!` or assertions in a function of type result. + /// Checks for usage of `panic!` or assertions in a function whose return type is `Result`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided. /// /// ### Known problems diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index 75066c1f0d2e2..80ef761906e04 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `panic!`. /// - /// ### Why is this bad? - /// `panic!` will stop the execution of the executable. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -31,8 +31,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unimplemented!`. /// - /// ### Why is this bad? - /// This macro should not be present in production code. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -48,9 +48,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `todo!`. /// - /// ### Why is this bad? - /// The `todo!` macro is often used for unfinished code, and it causes - /// code to panic. It should not be present in production code. + /// ### Why restrict this? + /// The `todo!` macro indicates the presence of unfinished code, + /// so it should not be present in production code. /// /// ### Example /// ```no_run @@ -70,8 +70,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unreachable!`. /// - /// ### Why is this bad? - /// This macro can cause code to panic. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/partial_pub_fields.rs b/clippy_lints/src/partial_pub_fields.rs index ffa403e27ca30..2d20cbea698f2 100644 --- a/clippy_lints/src/partial_pub_fields.rs +++ b/clippy_lints/src/partial_pub_fields.rs @@ -5,15 +5,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks whether partial fields of a struct are public. + /// Checks whether some but not all fields of a `struct` are public. /// /// Either make all fields of a type public, or make none of them public /// - /// ### Why is this bad? + /// ### Why restrict this? /// Most types should either be: /// * Abstract data types: complex objects with opaque implementation which guard - /// interior invariants and expose intentionally limited API to the outside world. - /// * Data: relatively simple objects which group a bunch of related attributes together. + /// interior invariants and expose intentionally limited API to the outside world. + /// * Data: relatively simple objects which group a bunch of related attributes together, + /// but have no invariants. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 44db061b8beed..9661a57b8b95c 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -30,9 +30,8 @@ declare_clippy_lint! { /// this lint can still be used to highlight areas of interest and ensure a good understanding /// of ownership semantics. /// - /// ### Why is this bad? - /// It isn't bad in general. But in some contexts it can be desirable - /// because it increases ownership hints in the code, and will guard against some changes + /// ### Why restrict this? + /// It increases ownership hints in the code, and will guard against some changes /// in ownership. /// /// ### Example diff --git a/clippy_lints/src/pub_use.rs b/clippy_lints/src/pub_use.rs index c0e999e76ef2f..ab8f8a1689dc3 100644 --- a/clippy_lints/src/pub_use.rs +++ b/clippy_lints/src/pub_use.rs @@ -5,13 +5,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// /// Restricts the usage of `pub use ...` /// - /// ### Why is this bad? - /// - /// `pub use` is usually fine, but a project may wish to limit `pub use` instances to prevent - /// unintentional exports or to encourage placing exported items directly in public modules + /// ### Why restrict this? + /// A project may wish to limit `pub use` instances to prevent + /// unintentional exports, or to encourage placing exported items directly in public modules. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs index ddfc53083c461..f5e6cb804da17 100644 --- a/clippy_lints/src/question_mark_used.rs +++ b/clippy_lints/src/question_mark_used.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for expressions that use the question mark operator and rejects them. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Sometimes code wants to avoid the question mark operator because for instance a local /// block requires a macro to re-throw errors to attach additional information to the /// error. diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index 7e71f48c6d9a6..3a00424545922 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -15,8 +15,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for raw string literals where a string literal can be used instead. /// - /// ### Why is this bad? - /// It's just unnecessary, but there are many cases where using a raw string literal is more + /// ### Why restrict this? + /// For consistent style by using simpler string literals whenever possible. + /// + /// However, there are many cases where using a raw string literal is more /// idiomatic than a string literal, so it's opt-in. /// /// ### Example diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index c99b657c23a23..7f87d18e50238 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -46,7 +46,7 @@ declare_clippy_lint! { /// Checks for slicing expressions which are equivalent to dereferencing the /// value. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some people may prefer to dereference rather than slice. /// /// ### Example diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 11b95ee3a54ca..81556f3961416 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// ### What it does /// Warns about needless / redundant type annotations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Code without type annotations is shorter and in most cases /// more idiomatic and easier to modify. /// diff --git a/clippy_lints/src/ref_patterns.rs b/clippy_lints/src/ref_patterns.rs index 607a0740b8438..467038523b496 100644 --- a/clippy_lints/src/ref_patterns.rs +++ b/clippy_lints/src/ref_patterns.rs @@ -6,9 +6,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for usages of the `ref` keyword. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The `ref` keyword can be confusing for people unfamiliar with it, and often /// it is more concise to use `&` instead. + /// /// ### Example /// ```no_run /// let opt = Some(5); diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 7e27f70bcf947..8fdd19c549f5a 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// It lints if a struct has two methods with the same name: /// one from a trait, another not from trait. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Confusing. /// /// ### Example diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 0b3adfb7a4b8d..0e77acdfd77f2 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -11,8 +11,7 @@ declare_clippy_lint! { /// Suggests moving the semicolon after a block to the inside of the block, after its last /// expression. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests inside the block. /// Take a look at `semicolon_outside_block` for the other alternative. @@ -40,8 +39,7 @@ declare_clippy_lint! { /// /// Suggests moving the semicolon from a block's final expression outside of the block. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests outside the block. /// Take a look at `semicolon_inside_block` for the other alternative. diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 9db08acb03b21..80f5fd0b49441 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -15,10 +15,10 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while just changing reference level or mutability. /// - /// ### Why is this bad? - /// Not much, in fact it's a very common pattern in Rust - /// code. Still, some may opt to avoid it in their code base, they can set this - /// lint to `Warn`. + /// ### Why restrict this? + /// To require that what are formally distinct variables be given distinct names. + /// + /// See also `shadow_reuse` and `shadow_unrelated` for other restrictions on shadowing. /// /// ### Example /// ```no_run @@ -42,12 +42,13 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while reusing the original value. /// - /// ### Why is this bad? - /// Not too much, in fact it's a common pattern in Rust - /// code. Still, some argue that name shadowing like this hurts readability, + /// ### Why restrict this? + /// Some argue that name shadowing like this hurts readability, /// because a value may be bound to different things depending on position in /// the code. /// + /// See also `shadow_same` and `shadow_unrelated` for other restrictions on shadowing. + /// /// ### Example /// ```no_run /// let x = 2; @@ -70,11 +71,14 @@ declare_clippy_lint! { /// scope, either without an initialization or with one that does not even use /// the original value. /// - /// ### Why is this bad? - /// Name shadowing can hurt readability, especially in + /// ### Why restrict this? + /// Shadowing a binding with a closely related one is part of idiomatic Rust, + /// but shadowing a binding by accident with an unrelated one may indicate a mistake. + /// + /// Additionally, name shadowing in general can hurt readability, especially in /// large code bases, because it is easy to lose track of the active binding at - /// any place in the code. This can be alleviated by either giving more specific - /// names to bindings or introducing more scopes to contain the bindings. + /// any place in the code. If linting against all shadowing is desired, you may wish + /// to use the `shadow_same` and `shadow_reuse` lints as well. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_call_fn.rs b/clippy_lints/src/single_call_fn.rs index 2ce7e714c6424..c71bc3f7fcdec 100644 --- a/clippy_lints/src/single_call_fn.rs +++ b/clippy_lints/src/single_call_fn.rs @@ -13,13 +13,20 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions that are only used once. Does not lint tests. /// - /// ### Why is this bad? - /// It's usually not, splitting a function into multiple parts often improves readability and in - /// the case of generics, can prevent the compiler from duplicating the function dozens of - /// time; instead, only duplicating a thunk. But this can prevent segmentation across a - /// codebase, where many small functions are used only once. + /// ### Why restrict this? + /// If a function is only used once (perhaps because it used to be used more widely), + /// then the code could be simplified by moving that function's code into its caller. /// - /// Note: If this lint is used, prepare to allow this a lot. + /// However, there are reasons not to do this everywhere: + /// + /// * Splitting a large function into multiple parts often improves readability + /// by giving names to its parts. + /// * A function’s signature might serve a necessary purpose, such as constraining + /// the type of a closure passed to it. + /// * Generic functions might call non-generic functions to reduce duplication + /// in the produced machine code. + /// + /// If this lint is used, prepare to `#[allow]` it a lot. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index 42f1564db353a..72feb977c3104 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -9,11 +9,10 @@ declare_clippy_lint! { /// Checks for lifetimes with names which are one character /// long. /// - /// ### Why is this bad? + /// ### Why restrict this? /// A single character is likely not enough to express the /// purpose of a lifetime. Using a longer name can make code - /// easier to understand, especially for those who are new to - /// Rust. + /// easier to understand. /// /// ### Known problems /// Rust programmers and learning resources tend to use single diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 926c56332cc19..12b70075a3d5e 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -12,11 +12,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates /// migrating to become `no_std` compatible. @@ -37,11 +35,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `alloc`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful /// for crates migrating to become `no_std` compatible. @@ -63,11 +59,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `alloc` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint /// is also useful for crates migrating to become `no_std` compatible. diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 292124196ff64..f8d36d6b92f61 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -45,10 +45,10 @@ declare_clippy_lint! { /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* /// match. /// - /// ### Why is this bad? - /// It's not bad in and of itself. However, this particular + /// ### Why restrict this? + /// This particular /// `Add` implementation is asymmetric (the other operand need not be `String`, - /// but `x` does), while addition as mathematically defined is symmetric, also + /// but `x` does), while addition as mathematically defined is symmetric, and /// the `String::push_str(_)` function is a perfectly good replacement. /// Therefore, some dislike it and wish not to have it in their code. /// @@ -123,7 +123,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for slice operations on strings /// - /// ### Why is this bad? + /// ### Why restrict this? /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character /// counts and string indices. This may lead to panics, and should warrant some test cases /// containing wide UTF-8 characters. This lint is most useful in code that should avoid @@ -364,10 +364,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `&str`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better - /// expressed with `.to_owned()`. + /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be + /// more specifically expressed with `.to_owned()`. /// /// ### Example /// ```no_run @@ -415,9 +415,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`. + /// When called on a `String` it only clones the `String`, which can be more specifically + /// expressed with `.clone()`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs index 1cc27670fa8bf..d150a5f858aa3 100644 --- a/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -11,8 +11,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// It's most probably a typo and may lead to unexpected behaviours. + /// /// ### Example /// ```no_run /// let x = 3_i32 ^ 4_i32; diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs index da55758264750..58e42892c41de 100644 --- a/clippy_lints/src/tests_outside_test_module.rs +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -11,9 +11,11 @@ declare_clippy_lint! { /// ### What it does /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module /// (marked with `#[cfg(test)]`). - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), /// having test functions outside of this module is confusing and may lead to them being "hidden". + /// /// ### Example /// ```no_run /// #[test] diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 5e45ab211efd8..62ef65ca122f7 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -217,7 +217,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc` and `Arc` when `T` is a mutable buffer type such as `String` or `Vec`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Expressions such as `Rc` usually have no advantage over `Rc`, since /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow`. /// @@ -274,7 +274,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc>`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `Rc` is used in single thread and `Mutex` is used in multi thread. /// Consider using `Rc>` in single thread or `Arc>` in multi thread. /// diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index cbd1618007700..7e8c11cf25a3a 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -38,10 +38,9 @@ declare_clippy_lint! { /// ); /// ``` /// - /// ### Why is this bad? - /// Undocumented unsafe blocks and impls can make it difficult to - /// read and maintain code, as well as uncover unsoundness - /// and bugs. + /// ### Why restrict this? + /// Undocumented unsafe blocks and impls can make it difficult to read and maintain code. + /// Writing out the safety justification may help in discovering unsoundness or bugs. /// /// ### Example /// ```no_run @@ -67,7 +66,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `// SAFETY: ` comments on safe code. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe code has no safety requirements, so there is no need to /// describe safety invariants. /// diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index 3d319b9fe767a..d42697b31d1f2 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for non-ASCII characters in string and char literals. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Yeah, we know, the 90's called and wanted their charset /// back. Even so, there still are editors and other programs out there that /// don't work well with Unicode. So if the code is meant to be used diff --git a/clippy_lints/src/unnecessary_self_imports.rs b/clippy_lints/src/unnecessary_self_imports.rs index 528a1dfcfc10f..93dff1b85796b 100644 --- a/clippy_lints/src/unnecessary_self_imports.rs +++ b/clippy_lints/src/unnecessary_self_imports.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for imports ending in `::{self}`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In most cases, this can be written much more cleanly by omitting `::{self}`. /// /// ### Known problems diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index aca500590cef1..197ab0f173bd8 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -13,8 +13,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions of type `Result` that contain `expect()` or `unwrap()` /// - /// ### Why is this bad? - /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics. + /// ### Why restrict this? + /// These functions promote recoverable errors to non-recoverable errors, + /// which may be undesirable in code bases which wish to avoid panics, + /// or be a bug in the specific function. /// /// ### Known problems /// This can cause false positives in functions that handle both recoverable and non recoverable errors. diff --git a/clippy_lints/src/visibility.rs b/clippy_lints/src/visibility.rs index 9818b98dd5b40..11dcceca7abb1 100644 --- a/clippy_lints/src/visibility.rs +++ b/clippy_lints/src/visibility.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `pub()` with `in`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_without_shorthand` lint for an alternative. @@ -57,7 +57,7 @@ declare_clippy_lint! { /// Note: As you cannot write a module's path in `pub()`, this will only trigger on /// `pub(super)` and the like. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_with_shorthand` lint for an alternative. diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index ff6ee0d10ad56..652ce88bd9516 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -66,7 +66,7 @@ declare_clippy_lint! { /// Checks for printing on *stdout*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stdout* while debugging an /// application and might forget to remove those prints afterward. /// @@ -88,7 +88,7 @@ declare_clippy_lint! { /// Checks for printing on *stderr*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stderr* while debugging an /// application and might forget to remove those prints afterward. /// @@ -110,15 +110,18 @@ declare_clippy_lint! { /// Checks for usage of `Debug` formatting. The purpose of this /// lint is to catch debugging remnants. /// - /// ### Why is this bad? - /// The purpose of the `Debug` trait is to facilitate - /// debugging Rust code. It should not be used in user-facing output. + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. /// /// ### Example /// ```no_run /// # let foo = "bar"; /// println!("{:?}", foo); /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability #[clippy::version = "pre 1.29.0"] pub USE_DEBUG, restriction, From 7f30b20b28f44e37df29f4de2c74a86d35b1b72b Mon Sep 17 00:00:00 2001 From: Jakob Schwarz Date: Wed, 22 May 2024 09:01:37 +0200 Subject: [PATCH 26/34] fulfill expectations in `check_partial_eq_without_eq` changelog: fulfill expectations in [derive_partial_eq_without_eq] --- clippy_lints/src/derive.rs | 17 +++++++++---- tests/ui/derive_partial_eq_without_eq.fixed | 17 +++++++++++++ tests/ui/derive_partial_eq_without_eq.rs | 17 +++++++++++++ tests/ui/derive_partial_eq_without_eq.stderr | 26 ++++++++++---------- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 19dedceae6d48..ecdef0eee54b6 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; +use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; use rustc_errors::Applicability; @@ -456,20 +456,27 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r && !has_non_exhaustive_attr(cx.tcx, *adt) && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) && let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) + && let Some(local_def_id) = adt.did().as_local() // If all of our fields implement `Eq`, we can implement `Eq` too && adt .all_fields() .map(|f| f.ty(cx.tcx, args)) .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, None, &[])) { - span_lint_and_sugg( + span_lint_hir_and_then( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, + cx.tcx.local_def_id_to_hir_id(local_def_id), span.ctxt().outer_expn_data().call_site, "you are deriving `PartialEq` and can implement `Eq`", - "consider deriving `Eq` as well", - "PartialEq, Eq".to_string(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + span.ctxt().outer_expn_data().call_site, + "consider deriving `Eq` as well", + "PartialEq, Eq", + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed index 6f42487bbf492..eb93eb8e8ed4e 100644 --- a/tests/ui/derive_partial_eq_without_eq.fixed +++ b/tests/ui/derive_partial_eq_without_eq.fixed @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs index 24f687c6c9db9..42dc435bdd52a 100644 --- a/tests/ui/derive_partial_eq_without_eq.rs +++ b/tests/ui/derive_partial_eq_without_eq.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr index 3d92112dc36d4..29cd7da6b77d0 100644 --- a/tests/ui/derive_partial_eq_without_eq.stderr +++ b/tests/ui/derive_partial_eq_without_eq.stderr @@ -1,5 +1,5 @@ error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:11:17 + --> tests/ui/derive_partial_eq_without_eq.rs:12:17 | LL | #[derive(Debug, PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` @@ -8,73 +8,73 @@ LL | #[derive(Debug, PartialEq)] = help: to override `-D warnings` add `#[allow(clippy::derive_partial_eq_without_eq)]` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:53:10 + --> tests/ui/derive_partial_eq_without_eq.rs:70:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:59:10 + --> tests/ui/derive_partial_eq_without_eq.rs:76:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:65:10 + --> tests/ui/derive_partial_eq_without_eq.rs:82:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:68:10 + --> tests/ui/derive_partial_eq_without_eq.rs:85:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:74:10 + --> tests/ui/derive_partial_eq_without_eq.rs:91:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:80:10 + --> tests/ui/derive_partial_eq_without_eq.rs:97:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:93:17 + --> tests/ui/derive_partial_eq_without_eq.rs:110:17 | LL | #[derive(Debug, PartialEq, Clone)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:96:10 + --> tests/ui/derive_partial_eq_without_eq.rs:113:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:103:14 + --> tests/ui/derive_partial_eq_without_eq.rs:120:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:106:14 + --> tests/ui/derive_partial_eq_without_eq.rs:123:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:166:14 + --> tests/ui/derive_partial_eq_without_eq.rs:183:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:174:14 + --> tests/ui/derive_partial_eq_without_eq.rs:191:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` From 714e172ef215f6c57a2449175278f6e0c330a2af Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 24 May 2024 17:25:50 +0100 Subject: [PATCH 27/34] Remove `DefId` from `EarlyParamRegion` (clippy/smir) --- clippy_lints/src/ptr.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 65929cd5fea9c..38580dc58226a 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -460,13 +460,19 @@ fn check_fn_args<'cx, 'tcx: 'cx>( } None }) { - if !lifetime.is_anonymous() + if let LifetimeName::Param(param_def_id) = lifetime.res + && !lifetime.is_anonymous() && fn_sig .output() .walk() .filter_map(|arg| { arg.as_region().and_then(|lifetime| match lifetime.kind() { - ty::ReEarlyParam(r) => Some(r.def_id), + ty::ReEarlyParam(r) => Some( + cx.tcx + .generics_of(cx.tcx.parent(param_def_id.to_def_id())) + .region_param(r, cx.tcx) + .def_id, + ), ty::ReBound(_, r) => r.kind.get_id(), ty::ReLateParam(r) => r.bound_region.get_id(), ty::ReStatic @@ -476,14 +482,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( | ty::ReError(_) => None, }) }) - .any(|def_id| { - matches!( - lifetime.res, - LifetimeName::Param(param_def_id) if def_id - .as_local() - .is_some_and(|def_id| def_id == param_def_id), - ) - }) + .any(|def_id| def_id.as_local().is_some_and(|def_id| def_id == param_def_id)) { // `&Cow<'a, T>` when the return type uses 'a is okay return None; From 17cc0a3a7d286b36a5745aafbfff7ac662270b5b Mon Sep 17 00:00:00 2001 From: Hamir Mahal Date: Fri, 24 May 2024 14:30:42 -0700 Subject: [PATCH 28/34] feat: auto-fix for `bare URLs` in doc comments --- clippy_lints/src/doc/markdown.rs | 8 +++++--- tests/ui/doc/doc-fixable.fixed | 3 +++ tests/ui/doc/doc-fixable.rs | 3 +++ tests/ui/doc/doc-fixable.stderr | 8 +++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 1add02af31017..8a497b5ab0d93 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, SuggestionStyle}; @@ -92,13 +92,15 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b if let Ok(url) = Url::parse(word) { // try to get around the fact that `foo::bar` parses as a valid URL if !url.cannot_be_a_base() { - span_lint( + span_lint_and_sugg( cx, DOC_MARKDOWN, span, "you should put bare URLs between `<`/`>` or make a proper Markdown link", + "try", + format!("<{word}>"), + Applicability::MachineApplicable, ); - return; } } diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 7e22c847b1bc7..84673f1f43fd5 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -240,3 +240,6 @@ extern { /// `foo()` fn in_extern(); } + +/// +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 3e2cb0df54b0e..4d017a99e0fb6 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -240,3 +240,6 @@ extern { /// foo() fn in_extern(); } + +/// https://github.com/rust-lang/rust-clippy/pull/12836 +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index cd2228c47e35c..a9263f62d38dc 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -363,5 +363,11 @@ help: try LL | /// `foo()` | ~~~~~~~ -error: aborting due to 33 previous errors +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> tests/ui/doc/doc-fixable.rs:244:5 + | +LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` + +error: aborting due to 34 previous errors From e61288cbf053cd313b7539481b3faee1ec351228 Mon Sep 17 00:00:00 2001 From: finga Date: Sat, 25 May 2024 21:36:49 +0200 Subject: [PATCH 29/34] book: Fix example code Fix example code of the "Disabling evaluation of certain code" section in the configuration chapter. --- book/src/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 9eb067abd91ec..ea549e4df4a51 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -133,7 +133,7 @@ Very rarely, you may wish to prevent Clippy from evaluating certain sections of `clippy` cfg is not set. You may need to provide a stub so that the code compiles: ```rust -#[cfg(not(clippy)] +#[cfg(not(clippy))] include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); #[cfg(clippy)] From 7110f471d305313b0ef12f66c2db3acc3c822851 Mon Sep 17 00:00:00 2001 From: cookie-s Date: Sun, 26 May 2024 22:39:23 -0400 Subject: [PATCH 30/34] [`many_single_char_names`]: Deduplicate diagnostics --- clippy_lints/src/non_expressive_names.rs | 4 ++++ tests/ui/many_single_char_names.rs | 2 -- tests/ui/many_single_char_names.stderr | 10 +++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 7b26235291a56..eacfe9ff328df 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -98,6 +98,10 @@ struct SimilarNamesLocalVisitor<'a, 'tcx> { impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { fn check_single_char_names(&self) { + if self.single_char_names.last().map(Vec::len) == Some(0) { + return; + } + let num_single_char_names = self.single_char_names.iter().flatten().count(); let threshold = self.lint.single_char_binding_names_threshold; if num_single_char_names as u64 > threshold { diff --git a/tests/ui/many_single_char_names.rs b/tests/ui/many_single_char_names.rs index 2af45eaab8af4..68578340d90e1 100644 --- a/tests/ui/many_single_char_names.rs +++ b/tests/ui/many_single_char_names.rs @@ -1,5 +1,3 @@ -//@compile-flags: -Zdeduplicate-diagnostics=yes - #![allow(clippy::too_many_arguments, clippy::diverging_sub_expression)] #![warn(clippy::many_single_char_names)] diff --git a/tests/ui/many_single_char_names.stderr b/tests/ui/many_single_char_names.stderr index 3b2460b5c077e..131836ef7c882 100644 --- a/tests/ui/many_single_char_names.stderr +++ b/tests/ui/many_single_char_names.stderr @@ -1,5 +1,5 @@ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -14,7 +14,7 @@ LL | let e: i32; = help: to override `-D warnings` add `#[allow(clippy::many_single_char_names)]` error: 6 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -28,7 +28,7 @@ LL | let f: i32; | ^ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -40,13 +40,13 @@ LL | e => panic!(), | ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:36:13 + --> tests/ui/many_single_char_names.rs:34:13 | LL | fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} | ^ ^ ^ ^ ^ ^ ^ ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:40:10 + --> tests/ui/many_single_char_names.rs:38:10 | LL | let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); | ^ ^ ^ ^ ^ ^ ^ ^ From 03306b6ab61bfe5bab6e555caf8ee0ba911cce9a Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 27 May 2024 11:49:10 +0800 Subject: [PATCH 31/34] suggest adding parentheses when linting [`let_and_return`] and [`needless_return`] --- clippy_lints/src/dereference.rs | 14 +++--- clippy_lints/src/needless_bool.rs | 13 ++---- clippy_lints/src/returns.rs | 34 ++++++--------- clippy_utils/src/lib.rs | 22 ++++++++++ tests/ui/let_and_return.fixed | 34 +++++++++++++++ tests/ui/let_and_return.rs | 34 +++++++++++++++ tests/ui/let_and_return.stderr | 72 ++++++++++++++++++++++++++++++- tests/ui/needless_return.fixed | 4 ++ tests/ui/needless_return.rs | 4 ++ tests/ui/needless_return.stderr | 14 +++++- 10 files changed, 203 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index c6aef9ac2d606..0276c64ef9b71 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -2,7 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; -use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode}; +use clippy_utils::{ + expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, +}; use core::mem; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; @@ -1038,14 +1040,8 @@ fn report<'tcx>( ); }, State::ExplicitDeref { mutability } => { - if matches!( - expr.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(_) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() + if is_block_like(expr) + && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() && ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index f9ee4a3dc93a2..e1866eaa18a7c 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment, - SpanlessEq, + higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, + span_extract_comment, SpanlessEq, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool { | ExprKind::Type(i, _) | ExprKind::Index(i, _, _) = inner.kind { - if matches!( - i.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(..) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) { + if is_block_like(i) { return true; } inner = i; diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index e8f9d43810473..48d6fb3c0378a 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -3,8 +3,8 @@ use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::{ - fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg, - span_find_starting_semi, + binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, + path_to_local_id, span_contains_cfg, span_find_starting_semi, }; use core::ops::ControlFlow; use rustc_errors::Applicability; @@ -129,7 +129,7 @@ enum RetReplacement<'tcx> { Empty, Block, Unit, - IfSequence(Cow<'tcx, str>, Applicability), + NeedsPar(Cow<'tcx, str>, Applicability), Expr(Cow<'tcx, str>, Applicability), } @@ -139,13 +139,13 @@ impl<'tcx> RetReplacement<'tcx> { Self::Empty | Self::Expr(..) => "remove `return`", Self::Block => "replace `return` with an empty block", Self::Unit => "replace `return` with a unit value", - Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", + Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", } } fn applicability(&self) -> Applicability { match self { - Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap, + Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, _ => Applicability::MachineApplicable, } } @@ -157,7 +157,7 @@ impl<'tcx> Display for RetReplacement<'tcx> { Self::Empty => write!(f, ""), Self::Block => write!(f, "{{}}"), Self::Unit => write!(f, "()"), - Self::IfSequence(inner, _) => write!(f, "({inner})"), + Self::NeedsPar(inner, _) => write!(f, "({inner})"), Self::Expr(inner, _) => write!(f, "{inner}"), } } @@ -244,7 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for Return { err.span_label(local.span, "unnecessary `let` binding"); if let Some(mut snippet) = snippet_opt(cx, initexpr.span) { - if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if binary_expr_needs_parentheses(initexpr) { + if !has_enclosing_paren(&snippet) { + snippet = format!("({snippet})"); + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { if !has_enclosing_paren(&snippet) { snippet = format!("({snippet})"); } @@ -349,8 +353,8 @@ fn check_final_expr<'tcx>( let mut applicability = Applicability::MachineApplicable; let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); - if expr_contains_conjunctive_ifs(inner_expr) { - RetReplacement::IfSequence(snippet, applicability) + if binary_expr_needs_parentheses(inner_expr) { + RetReplacement::NeedsPar(snippet, applicability) } else { RetReplacement::Expr(snippet, applicability) } @@ -404,18 +408,6 @@ fn check_final_expr<'tcx>( } } -fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool { - fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool { - match expr.kind { - ExprKind::If(..) => on_if, - ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true), - _ => false, - } - } - - contains_if(expr, false) -} - fn emit_return_lint( cx: &LateContext<'_>, ret_span: Span, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 4c603bda770a9..f0870e1b0e49e 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) ) } + +/// Returns true if the given `expr` is a block or resembled as a block, +/// such as `if`, `loop`, `match` expressions etc. +pub fn is_block_like(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) + ) +} + +/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses. +pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool { + fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool { + match expr.kind { + ExprKind::Binary(_, lhs, _) => contains_block(lhs, true), + _ if is_block_like(expr) => is_operand, + _ => false, + } + } + + contains_block(expr, false) +} diff --git a/tests/ui/let_and_return.fixed b/tests/ui/let_and_return.fixed index 4187019e58944..b68b41cdca237 100644 --- a/tests/ui/let_and_return.fixed +++ b/tests/ui/let_and_return.fixed @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + + (if true { "a".to_string() } else { "b".to_string() } + "c") + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + + "c".to_string() + if true { "a" } else { "b" } + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + + ({ "a".to_string() } + "b" + { "c" } + "d") + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + + (if true { 2 } else { 3 } << 4) + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + + ({ true } || { false } && { 2 <= 3 }) + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 54444957b7d53..6b9035f942880 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + let s = "c".to_string() + if true { "a" } else { "b" }; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + let s = { "a".to_string() } + "b" + { "c" } + "d"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + let s = if true { 2 } else { 3 } << 4; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + let s = { true } || { false } && { 2 <= 3 }; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.stderr b/tests/ui/let_and_return.stderr index ff5962ec196e6..75efa05d770a8 100644 --- a/tests/ui/let_and_return.stderr +++ b/tests/ui/let_and_return.stderr @@ -78,5 +78,75 @@ LL + E::B(x) => x, LL + }) as _ | -error: aborting due to 5 previous errors +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:216:9 + | +LL | let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + | ------------------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { "a".to_string() } else { "b".to_string() } + "c") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:222:9 + | +LL | let s = "c".to_string() + if true { "a" } else { "b" }; + | ------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ "c".to_string() + if true { "a" } else { "b" } + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:228:9 + | +LL | let s = { "a".to_string() } + "b" + { "c" } + "d"; + | -------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ "a".to_string() } + "b" + { "c" } + "d") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:236:13 + | +LL | let s = if true { 2 } else { 3 } << 4; + | -------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { 2 } else { 3 } << 4) + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:241:13 + | +LL | let s = { true } || { false } && { 2 <= 3 }; + | -------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ true } || { false } && { 2 <= 3 }) + | + +error: aborting due to 10 previous errors diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 2575f2449e181..a9271cb399d8a 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -323,4 +323,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + ({ "a".to_string() } + "b" + { "c" }) +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 04f21834d8853..dc888bf667f15 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -333,4 +333,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + return { "a".to_string() } + "b" + { "c" }; +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 758ff6d985cd8..bf5a89d8b75d0 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -653,5 +653,17 @@ LL - return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) | -error: aborting due to 52 previous errors +error: unneeded `return` statement + --> tests/ui/needless_return.rs:337:5 + | +LL | return { "a".to_string() } + "b" + { "c" }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove `return` and wrap the sequence with parentheses + | +LL - return { "a".to_string() } + "b" + { "c" }; +LL + ({ "a".to_string() } + "b" + { "c" }) + | + +error: aborting due to 53 previous errors From 4a64180dd5cc48a26b2af3fbe0ddd3da0cccfb82 Mon Sep 17 00:00:00 2001 From: Marc Dominik Migge Date: Fri, 24 May 2024 17:59:09 +0200 Subject: [PATCH 32/34] `unnecessary_to_owned` should not suggest to remove `&` in macro expansion --- .../src/methods/unnecessary_iter_cloned.rs | 16 +++---- clippy_lints/src/methods/utils.rs | 16 ++++--- tests/ui/unnecessary_iter_cloned.fixed | 29 ++++++++++++ tests/ui/unnecessary_iter_cloned.rs | 29 ++++++++++++ tests/ui/unnecessary_iter_cloned.stderr | 44 +++++++++++++++++-- tests/ui/unnecessary_to_owned.stderr | 2 +- 6 files changed, 117 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 7431dc1cf0b16..d1300dd43c283 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -38,7 +38,7 @@ pub fn check_for_loop_iter( ) -> bool { if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) - && let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body) + && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) && !clone_or_copy_needed && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) { @@ -123,14 +123,12 @@ pub fn check_for_loop_iter( Applicability::MachineApplicable }; diag.span_suggestion(expr.span, "use", snippet, applicability); - for addr_of_expr in addr_of_exprs { - match addr_of_expr.kind { - ExprKind::AddrOf(_, _, referent) => { - let span = addr_of_expr.span.with_hi(referent.span.lo()); - diag.span_suggestion(span, "remove this `&`", "", applicability); - }, - _ => unreachable!(), - } + if !references_to_binding.is_empty() { + diag.multipart_suggestion( + "remove any references to the binding", + references_to_binding, + applicability, + ); } }, ); diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 34d7b9acbe4b0..1a55b7160fb18 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -9,6 +9,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; +use rustc_span::Span; pub(super) fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, @@ -96,15 +97,15 @@ pub(super) fn clone_or_copy_needed<'tcx>( cx: &LateContext<'tcx>, pat: &Pat<'tcx>, body: &'tcx Expr<'tcx>, -) -> (bool, Vec<&'tcx Expr<'tcx>>) { +) -> (bool, Vec<(Span, String)>) { let mut visitor = CloneOrCopyVisitor { cx, binding_hir_ids: pat_bindings(pat), clone_or_copy_needed: false, - addr_of_exprs: Vec::new(), + references_to_binding: Vec::new(), }; visitor.visit_expr(body); - (visitor.clone_or_copy_needed, visitor.addr_of_exprs) + (visitor.clone_or_copy_needed, visitor.references_to_binding) } /// Returns a vector of all `HirId`s bound by the pattern. @@ -127,7 +128,7 @@ struct CloneOrCopyVisitor<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, binding_hir_ids: Vec, clone_or_copy_needed: bool, - addr_of_exprs: Vec<&'tcx Expr<'tcx>>, + references_to_binding: Vec<(Span, String)>, } impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { @@ -142,8 +143,11 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { if self.is_binding(expr) { if let Some(parent) = get_parent_expr(self.cx, expr) { match parent.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { - self.addr_of_exprs.push(parent); + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, referent) => { + if !parent.span.from_expansion() { + self.references_to_binding + .push((parent.span.until(referent.span), String::new())); + } return; }, ExprKind::MethodCall(.., args, _) => { diff --git a/tests/ui/unnecessary_iter_cloned.fixed b/tests/ui/unnecessary_iter_cloned.fixed index 2c582c90ba8ce..dc5e163ff04e4 100644 --- a/tests/ui/unnecessary_iter_cloned.fixed +++ b/tests/ui/unnecessary_iter_cloned.fixed @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + let ref_i = i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.rs b/tests/ui/unnecessary_iter_cloned.rs index a28ccd1efef26..8f797ac717fb6 100644 --- a/tests/ui/unnecessary_iter_cloned.rs +++ b/tests/ui/unnecessary_iter_cloned.rs @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + let ref_i = &i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.stderr b/tests/ui/unnecessary_iter_cloned.stderr index fb98cfddc262f..0bdb37a521fca 100644 --- a/tests/ui/unnecessary_iter_cloned.stderr +++ b/tests/ui/unnecessary_iter_cloned.stderr @@ -10,7 +10,7 @@ help: use | LL | for (t, path) in files { | ~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { @@ -26,11 +26,49 @@ help: use | LL | for (t, path) in files.iter() { | ~~~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { | -error: aborting due to 2 previous errors +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:177:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ help: use: `v.iter()` + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:185:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for c in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL - let ref_c = &c; +LL + let ref_c = c; + | + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:194:23 + | +LL | for (i, c) in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for (i, c) in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL ~ let ref_c = c; +LL ~ let ref_i = i; + | + +error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 5475df9c7b936..2829f3cd6e980 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -487,7 +487,7 @@ help: use | LL | for t in file_types { | ~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { From e3e27ba3dd5a5bd9b36f03527ef9fff82cb17dc4 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Fri, 3 May 2024 09:22:55 +0000 Subject: [PATCH 33/34] Create const block DefIds in typeck instead of ast lowering --- clippy_utils/src/consts.rs | 6 ++--- clippy_utils/src/hir_utils.rs | 4 ++-- tests/ui/arithmetic_side_effects.stderr | 32 +++++++++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 253ae3aca6894..f5d3967d130db 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -6,7 +6,7 @@ use crate::{clip, is_direct_expn_of, sext, unsext}; use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; +use rustc_hir::{BinOp, BinOpKind, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp}; use rustc_lexer::tokenize; use rustc_lint::LateContext; use rustc_middle::mir::interpret::{alloc_range, Scalar}; @@ -412,7 +412,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { - ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => { self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| { @@ -491,7 +491,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// leaves the local crate. pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option { match e.kind { - ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr_is_empty(e), ExprKind::Path(ref qpath) => { if !self diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 9f285621e0c96..cc5ccd4053a2f 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -295,7 +295,7 @@ impl HirEqInterExpr<'_, '_, '_> { self.eq_expr(lx, rx) && self.eq_ty(lt, rt) }, (&ExprKind::Closure(_l), &ExprKind::Closure(_r)) => false, - (&ExprKind::ConstBlock(lb), &ExprKind::ConstBlock(rb)) => self.eq_body(lb.body, rb.body), + (&ExprKind::ConstBlock(lb), &ExprKind::ConstBlock(rb)) => self.eq_expr(lb, rb), (&ExprKind::Continue(li), &ExprKind::Continue(ri)) => { both(&li.label, &ri.label, |l, r| l.ident.name == r.ident.name) }, @@ -770,7 +770,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(self.cx.tcx.hir().body(body).value); }, ExprKind::ConstBlock(ref l_id) => { - self.hash_body(l_id.body); + self.hash_expr(l_id); }, ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => { self.hash_expr(e); diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 8039c0bfa2484..df14ff396f6cf 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,12 +1,36 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:304:5 + --> tests/ui/arithmetic_side_effects.rs:188:36 | -LL | _n += 1; - | ^^^^^^^ +LL | let _ = const { let mut n = 1; n += 1; n }; + | ^^^^^^ | = note: `-D clippy::arithmetic-side-effects` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:191:40 + | +LL | let _ = const { let mut n = 1; n = n + 1; n }; + | ^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:194:40 + | +LL | let _ = const { let mut n = 1; n = 1 + n; n }; + | ^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:200:59 + | +LL | let _ = const { let mut n = 1; n = -1; n = -(-1); n = -n; n }; + | ^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:304:5 + | +LL | _n += 1; + | ^^^^^^^ + error: arithmetic operation that can potentially result in unexpected side-effects --> tests/ui/arithmetic_side_effects.rs:305:5 | @@ -727,5 +751,5 @@ error: arithmetic operation that can potentially result in unexpected side-effec LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 121 previous errors +error: aborting due to 125 previous errors From 280ed2b594783a3c5fc91722eb37830ab78f1f5a Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 30 May 2024 09:44:33 +0200 Subject: [PATCH 34/34] Bump nightly version -> 2024-05-30 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index a0585ffdb45b0..dd8b9ece773e7 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-05-16" +channel = "nightly-2024-05-30" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]