From 18f91397600365db7ab9f0bf01fdef42798db176 Mon Sep 17 00:00:00 2001 From: Mihir Date: Mon, 29 Jun 2026 22:42:46 +0530 Subject: [PATCH 1/7] =?UTF-8?q?=EF=BB=BFAdd=20ASSERTIONS=5FON=5FCOLLECTION?= =?UTF-8?q?=5FEMPTINESS=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggests replacing ssert!(x.is_empty()) and ssert!(!x.is_empty()) with ssert_eq!(x, []) / ssert_ne!(x, []) (or ssert_eq!(x, "") / ssert_ne!(x, "") for String types), so the collection contents are shown on failure. --- .../src/assertions_on_collection_emptiness.rs | 107 ++++++++++++++++++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + .../assertions_on_collection_emptiness.fixed | 29 +++++ .../ui/assertions_on_collection_emptiness.rs | 29 +++++ .../assertions_on_collection_emptiness.stderr | 29 +++++ 6 files changed, 197 insertions(+) create mode 100644 clippy_lints/src/assertions_on_collection_emptiness.rs create mode 100644 tests/ui/assertions_on_collection_emptiness.fixed create mode 100644 tests/ui/assertions_on_collection_emptiness.rs create mode 100644 tests/ui/assertions_on_collection_emptiness.stderr diff --git a/clippy_lints/src/assertions_on_collection_emptiness.rs b/clippy_lints/src/assertions_on_collection_emptiness.rs new file mode 100644 index 000000000000..a4ba47f6697d --- /dev/null +++ b/clippy_lints/src/assertions_on_collection_emptiness.rs @@ -0,0 +1,107 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{find_assert_args, root_macro_call_first_node}; +use clippy_utils::res::MaybeDef; +use clippy_utils::source::snippet_with_context; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `assert!(s.is_empty())` and `assert!(!s.is_empty())`. + /// + /// ### Why is this bad? + /// `assert!` only reports the condition was false, not what the collection + /// contained. Using `assert_eq!` / `assert_ne!` with an empty literal + /// prints the collection contents on failure, making debugging faster. + /// + /// ### Example + /// ```no_run + /// # let items = vec![1, 2, 3]; + /// assert!(items.is_empty()); + /// assert!(!items.is_empty()); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let items = vec![1, 2, 3]; + /// assert_eq!(items, []); + /// assert_ne!(items, []); + /// ``` + #[clippy::version = "1.97.0"] + pub ASSERTIONS_ON_COLLECTION_EMPTINESS, + nursery, + "asserting on `.is_empty()` without showing the collection contents" +} + +declare_lint_pass!(AssertionsOnCollectionEmptiness => [ASSERTIONS_ON_COLLECTION_EMPTINESS]); + +impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let Some(macro_call) = root_macro_call_first_node(cx, e) + && matches!( + cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::assert_macro | sym::debug_assert_macro), + ) + && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) + && panic_expn.is_default_message() + { + let method_name; + let recv; + let is_negated; + + match condition.kind { + ExprKind::MethodCall(ms, r, [], _) => { + method_name = ms.ident.name; + recv = r; + is_negated = false; + }, + ExprKind::Unary(UnOp::Not, inner) + if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => + { + method_name = ms.ident.name; + recv = r; + is_negated = true; + }, + _ => return, + }; + + if method_name != sym::is_empty { + return; + } + + let message = if is_negated { + "used `assert!` with a non-empty collection check" + } else { + "used `assert!` with an empty collection check" + }; + + let recv_ty = cx.typeck_results().expr_ty(recv); + let empty_literal = empty_literal_for_type(cx, recv_ty); + + span_lint_and_then(cx, ASSERTIONS_ON_COLLECTION_EMPTINESS, macro_call.span, message, |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snippet = + snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + + let sugg = if is_negated { + format!("assert_ne!({recv_snippet}, {empty_literal})") + } else { + format!("assert_eq!({recv_snippet}, {empty_literal})") + }; + diag.span_suggestion(macro_call.span, "replace with", sugg, app); + }); + } + } +} + +fn empty_literal_for_type(cx: &LateContext<'_>, ty: Ty<'_>) -> &'static str { + if ty.is_str() || ty.peel_refs().is_str() || ty.is_lang_item(cx, LangItem::String) { + "\"\"" + } else { + "[]" + } +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 51a848d022d8..c93cbab3d2c6 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -11,6 +11,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::as_conversions::AS_CONVERSIONS_INFO, crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, crate::asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX_INFO, + crate::assertions_on_collection_emptiness::ASSERTIONS_ON_COLLECTION_EMPTINESS_INFO, crate::assertions_on_constants::ASSERTIONS_ON_CONSTANTS_INFO, crate::assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES_INFO, crate::assigning_clones::ASSIGNING_CLONES_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8f550786819e..2d95dffd0aba 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -69,6 +69,7 @@ mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; mod asm_syntax; +mod assertions_on_collection_emptiness; mod assertions_on_constants; mod assertions_on_result_states; mod assigning_clones; @@ -665,6 +666,7 @@ rustc_lint::late_lint_methods!( RedundantClone: redundant_clone::RedundantClone = redundant_clone::RedundantClone, SlowVectorInit: slow_vector_initialization::SlowVectorInit = slow_vector_initialization::SlowVectorInit, UnnecessaryWraps: unnecessary_wraps::UnnecessaryWraps = unnecessary_wraps::UnnecessaryWraps::new(conf), + AssertionsOnCollectionEmptiness: assertions_on_collection_emptiness::AssertionsOnCollectionEmptiness = assertions_on_collection_emptiness::AssertionsOnCollectionEmptiness, AssertionsOnConstants: assertions_on_constants::AssertionsOnConstants = assertions_on_constants::AssertionsOnConstants::new(conf), AssertionsOnResultStates: assertions_on_result_states::AssertionsOnResultStates = assertions_on_result_states::AssertionsOnResultStates, InherentToString: inherent_to_string::InherentToString = inherent_to_string::InherentToString, diff --git a/tests/ui/assertions_on_collection_emptiness.fixed b/tests/ui/assertions_on_collection_emptiness.fixed new file mode 100644 index 000000000000..7607e3627a2c --- /dev/null +++ b/tests/ui/assertions_on_collection_emptiness.fixed @@ -0,0 +1,29 @@ +#![warn(clippy::assertions_on_collection_emptiness)] +#![allow(clippy::len_zero, clippy::comparison_to_empty)] + +fn main() { + // Test with Vec + let v: Vec = vec![]; + assert_eq!(v, []); + //~^ assertions_on_collection_emptiness + assert_ne!(v, []); + //~^ assertions_on_collection_emptiness + + // Test with String + let s = String::new(); + assert_eq!(s, ""); + //~^ assertions_on_collection_emptiness + assert_ne!(s, ""); + //~^ assertions_on_collection_emptiness + + // Should not lint: custom message + assert!(v.is_empty(), "vec is not empty"); + assert!(!v.is_empty(), "vec is empty"); + + // Should not lint: assert_ne!/assert_eq! already fine + assert_eq!(v, []); + assert_ne!(v, []); + + // Should not lint: not is_empty + assert!(v.len() == 0); +} diff --git a/tests/ui/assertions_on_collection_emptiness.rs b/tests/ui/assertions_on_collection_emptiness.rs new file mode 100644 index 000000000000..ba7b0c7da321 --- /dev/null +++ b/tests/ui/assertions_on_collection_emptiness.rs @@ -0,0 +1,29 @@ +#![warn(clippy::assertions_on_collection_emptiness)] +#![allow(clippy::len_zero, clippy::comparison_to_empty)] + +fn main() { + // Test with Vec + let v: Vec = vec![]; + assert!(v.is_empty()); + //~^ assertions_on_collection_emptiness + assert!(!v.is_empty()); + //~^ assertions_on_collection_emptiness + + // Test with String + let s = String::new(); + assert!(s.is_empty()); + //~^ assertions_on_collection_emptiness + assert!(!s.is_empty()); + //~^ assertions_on_collection_emptiness + + // Should not lint: custom message + assert!(v.is_empty(), "vec is not empty"); + assert!(!v.is_empty(), "vec is empty"); + + // Should not lint: assert_ne!/assert_eq! already fine + assert_eq!(v, []); + assert_ne!(v, []); + + // Should not lint: not is_empty + assert!(v.len() == 0); +} diff --git a/tests/ui/assertions_on_collection_emptiness.stderr b/tests/ui/assertions_on_collection_emptiness.stderr new file mode 100644 index 000000000000..9943c8146b84 --- /dev/null +++ b/tests/ui/assertions_on_collection_emptiness.stderr @@ -0,0 +1,29 @@ +error: used `assert!` with an empty collection check + --> tests/ui/assertions_on_collection_emptiness.rs:7:5 + | +LL | assert!(v.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_eq!(v, [])` + | + = note: `-D clippy::assertions-on-collection-emptiness` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::assertions_on_collection_emptiness)]` + +error: used `assert!` with a non-empty collection check + --> tests/ui/assertions_on_collection_emptiness.rs:9:5 + | +LL | assert!(!v.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_ne!(v, [])` + +error: used `assert!` with an empty collection check + --> tests/ui/assertions_on_collection_emptiness.rs:14:5 + | +LL | assert!(s.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_eq!(s, "")` + +error: used `assert!` with a non-empty collection check + --> tests/ui/assertions_on_collection_emptiness.rs:16:5 + | +LL | assert!(!s.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_ne!(s, "")` + +error: aborting due to 4 previous errors + From b1c84d905a03d71d095d1926d0ec8cadf152bd0e Mon Sep 17 00:00:00 2001 From: Mihir Date: Mon, 29 Jun 2026 23:10:04 +0530 Subject: [PATCH 2/7] Fix PR follow-up failures --- CHANGELOG.md | 1 + .../src/assertions_on_collection_emptiness.rs | 16 +++------------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 689d56d0fb7c..80748a533852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6639,6 +6639,7 @@ Released 2018-09-13 [`as_pointer_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_pointer_underscore [`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut [`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore +[`assertions_on_collection_emptiness`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_collection_emptiness [`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants [`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states [`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern diff --git a/clippy_lints/src/assertions_on_collection_emptiness.rs b/clippy_lints/src/assertions_on_collection_emptiness.rs index a4ba47f6697d..303e149d29ae 100644 --- a/clippy_lints/src/assertions_on_collection_emptiness.rs +++ b/clippy_lints/src/assertions_on_collection_emptiness.rs @@ -49,22 +49,12 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) && panic_expn.is_default_message() { - let method_name; - let recv; - let is_negated; - - match condition.kind { - ExprKind::MethodCall(ms, r, [], _) => { - method_name = ms.ident.name; - recv = r; - is_negated = false; - }, + let (method_name, recv, is_negated) = match condition.kind { + ExprKind::MethodCall(ms, r, [], _) => (ms.ident.name, r, false), ExprKind::Unary(UnOp::Not, inner) if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => { - method_name = ms.ident.name; - recv = r; - is_negated = true; + (ms.ident.name, r, true) }, _ => return, }; From 236a04933efa743841b2f9d18b00b064b55a4897 Mon Sep 17 00:00:00 2001 From: Mihir Date: Mon, 29 Jun 2026 23:18:43 +0530 Subject: [PATCH 3/7] Format assertions_on_collection_emptiness --- .../src/assertions_on_collection_emptiness.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/assertions_on_collection_emptiness.rs b/clippy_lints/src/assertions_on_collection_emptiness.rs index 303e149d29ae..abd908910630 100644 --- a/clippy_lints/src/assertions_on_collection_emptiness.rs +++ b/clippy_lints/src/assertions_on_collection_emptiness.rs @@ -51,9 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { { let (method_name, recv, is_negated) = match condition.kind { ExprKind::MethodCall(ms, r, [], _) => (ms.ident.name, r, false), - ExprKind::Unary(UnOp::Not, inner) - if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => - { + ExprKind::Unary(UnOp::Not, inner) if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => { (ms.ident.name, r, true) }, _ => return, @@ -72,18 +70,23 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { let recv_ty = cx.typeck_results().expr_ty(recv); let empty_literal = empty_literal_for_type(cx, recv_ty); - span_lint_and_then(cx, ASSERTIONS_ON_COLLECTION_EMPTINESS, macro_call.span, message, |diag| { - let mut app = Applicability::MachineApplicable; - let recv_snippet = - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + span_lint_and_then( + cx, + ASSERTIONS_ON_COLLECTION_EMPTINESS, + macro_call.span, + message, + |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snippet = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; - let sugg = if is_negated { - format!("assert_ne!({recv_snippet}, {empty_literal})") - } else { - format!("assert_eq!({recv_snippet}, {empty_literal})") - }; - diag.span_suggestion(macro_call.span, "replace with", sugg, app); - }); + let sugg = if is_negated { + format!("assert_ne!({recv_snippet}, {empty_literal})") + } else { + format!("assert_eq!({recv_snippet}, {empty_literal})") + }; + diag.span_suggestion(macro_call.span, "replace with", sugg, app); + }, + ); } } } From 9e5ec15fd3faf70897a213c6d9ea52bca5abc59a Mon Sep 17 00:00:00 2001 From: Mihir Date: Mon, 29 Jun 2026 23:29:40 +0530 Subject: [PATCH 4/7] Apply clippy_dev formatting --- clippy_lints/src/assertions_on_collection_emptiness.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/assertions_on_collection_emptiness.rs b/clippy_lints/src/assertions_on_collection_emptiness.rs index abd908910630..284b03238a7b 100644 --- a/clippy_lints/src/assertions_on_collection_emptiness.rs +++ b/clippy_lints/src/assertions_on_collection_emptiness.rs @@ -37,7 +37,9 @@ declare_clippy_lint! { "asserting on `.is_empty()` without showing the collection contents" } -declare_lint_pass!(AssertionsOnCollectionEmptiness => [ASSERTIONS_ON_COLLECTION_EMPTINESS]); +declare_lint_pass!(AssertionsOnCollectionEmptiness => [ + ASSERTIONS_ON_COLLECTION_EMPTINESS, +]); impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { From 1627e22bbacd29bdf4bcc61e2567d390b026afea Mon Sep 17 00:00:00 2001 From: Mihir Date: Tue, 7 Jul 2026 23:39:19 +0530 Subject: [PATCH 5/7] Address review feedback for assert is empty lint --- ...ection_emptiness.rs => assert_is_empty.rs} | 69 +++++++++++-------- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 +- ..._emptiness.fixed => assert_is_empty.fixed} | 10 +-- ...ection_emptiness.rs => assert_is_empty.rs} | 10 +-- ...mptiness.stderr => assert_is_empty.stderr} | 12 ++-- tests/ui/assert_is_empty_macro.rs | 24 +++++++ tests/ui/assert_is_empty_macro.stderr | 28 ++++++++ 8 files changed, 112 insertions(+), 47 deletions(-) rename clippy_lints/src/{assertions_on_collection_emptiness.rs => assert_is_empty.rs} (57%) rename tests/ui/{assertions_on_collection_emptiness.fixed => assert_is_empty.fixed} (71%) rename tests/ui/{assertions_on_collection_emptiness.rs => assert_is_empty.rs} (71%) rename tests/ui/{assertions_on_collection_emptiness.stderr => assert_is_empty.stderr} (68%) create mode 100644 tests/ui/assert_is_empty_macro.rs create mode 100644 tests/ui/assert_is_empty_macro.stderr diff --git a/clippy_lints/src/assertions_on_collection_emptiness.rs b/clippy_lints/src/assert_is_empty.rs similarity index 57% rename from clippy_lints/src/assertions_on_collection_emptiness.rs rename to clippy_lints/src/assert_is_empty.rs index 284b03238a7b..b761c85bbed6 100644 --- a/clippy_lints/src/assertions_on_collection_emptiness.rs +++ b/clippy_lints/src/assert_is_empty.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::{find_assert_args, root_macro_call_first_node}; +use clippy_utils::macros::{find_assert_args, first_node_macro_backtrace}; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; use clippy_utils::sym; @@ -32,29 +32,44 @@ declare_clippy_lint! { /// assert_ne!(items, []); /// ``` #[clippy::version = "1.97.0"] - pub ASSERTIONS_ON_COLLECTION_EMPTINESS, - nursery, + pub ASSERT_IS_EMPTY, + pedantic, "asserting on `.is_empty()` without showing the collection contents" } -declare_lint_pass!(AssertionsOnCollectionEmptiness => [ - ASSERTIONS_ON_COLLECTION_EMPTINESS, -]); +declare_lint_pass!(AssertIsEmpty => [ASSERT_IS_EMPTY]); -impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { +impl<'tcx> LateLintPass<'tcx> for AssertIsEmpty { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let Some(macro_call) = root_macro_call_first_node(cx, e) - && matches!( + if !matches!(e.kind, ExprKind::If(..)) { + return; + } + + if let Some(macro_call) = first_node_macro_backtrace(cx, e).find(|macro_call| { + matches!( cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro | sym::debug_assert_macro), ) - && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) + }) && matches!( + cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::assert_macro | sym::debug_assert_macro), + ) && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) && panic_expn.is_default_message() { - let (method_name, recv, is_negated) = match condition.kind { - ExprKind::MethodCall(ms, r, [], _) => (ms.ident.name, r, false), + let method_name; + let recv; + let is_negated; + + match condition.kind { + ExprKind::MethodCall(ms, r, [], _) => { + method_name = ms.ident.name; + recv = r; + is_negated = false; + }, ExprKind::Unary(UnOp::Not, inner) if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => { - (ms.ident.name, r, true) + method_name = ms.ident.name; + recv = r; + is_negated = true; }, _ => return, }; @@ -72,23 +87,21 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnCollectionEmptiness { let recv_ty = cx.typeck_results().expr_ty(recv); let empty_literal = empty_literal_for_type(cx, recv_ty); - span_lint_and_then( - cx, - ASSERTIONS_ON_COLLECTION_EMPTINESS, - macro_call.span, - message, - |diag| { - let mut app = Applicability::MachineApplicable; - let recv_snippet = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + span_lint_and_then(cx, ASSERT_IS_EMPTY, macro_call.span, message, |diag| { + let mut app = Applicability::MachineApplicable; + let recv_snippet = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; - let sugg = if is_negated { - format!("assert_ne!({recv_snippet}, {empty_literal})") - } else { - format!("assert_eq!({recv_snippet}, {empty_literal})") - }; + let sugg = if is_negated { + format!("assert_ne!({recv_snippet}, {empty_literal})") + } else { + format!("assert_eq!({recv_snippet}, {empty_literal})") + }; + if macro_call.span.from_expansion() { + diag.help(format!("replace with: `{sugg}`")); + } else { diag.span_suggestion(macro_call.span, "replace with", sugg, app); - }, - ); + } + }); } } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index c93cbab3d2c6..28745f5ea81b 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -11,7 +11,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::as_conversions::AS_CONVERSIONS_INFO, crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, crate::asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX_INFO, - crate::assertions_on_collection_emptiness::ASSERTIONS_ON_COLLECTION_EMPTINESS_INFO, + crate::assert_is_empty::ASSERT_IS_EMPTY_INFO, crate::assertions_on_constants::ASSERTIONS_ON_CONSTANTS_INFO, crate::assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES_INFO, crate::assigning_clones::ASSIGNING_CLONES_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 2d95dffd0aba..c94f653d12f4 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -69,7 +69,7 @@ mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; mod asm_syntax; -mod assertions_on_collection_emptiness; +mod assert_is_empty; mod assertions_on_constants; mod assertions_on_result_states; mod assigning_clones; @@ -666,7 +666,7 @@ rustc_lint::late_lint_methods!( RedundantClone: redundant_clone::RedundantClone = redundant_clone::RedundantClone, SlowVectorInit: slow_vector_initialization::SlowVectorInit = slow_vector_initialization::SlowVectorInit, UnnecessaryWraps: unnecessary_wraps::UnnecessaryWraps = unnecessary_wraps::UnnecessaryWraps::new(conf), - AssertionsOnCollectionEmptiness: assertions_on_collection_emptiness::AssertionsOnCollectionEmptiness = assertions_on_collection_emptiness::AssertionsOnCollectionEmptiness, + AssertIsEmpty: assert_is_empty::AssertIsEmpty = assert_is_empty::AssertIsEmpty, AssertionsOnConstants: assertions_on_constants::AssertionsOnConstants = assertions_on_constants::AssertionsOnConstants::new(conf), AssertionsOnResultStates: assertions_on_result_states::AssertionsOnResultStates = assertions_on_result_states::AssertionsOnResultStates, InherentToString: inherent_to_string::InherentToString = inherent_to_string::InherentToString, diff --git a/tests/ui/assertions_on_collection_emptiness.fixed b/tests/ui/assert_is_empty.fixed similarity index 71% rename from tests/ui/assertions_on_collection_emptiness.fixed rename to tests/ui/assert_is_empty.fixed index 7607e3627a2c..a386c4e175c9 100644 --- a/tests/ui/assertions_on_collection_emptiness.fixed +++ b/tests/ui/assert_is_empty.fixed @@ -1,20 +1,20 @@ -#![warn(clippy::assertions_on_collection_emptiness)] +#![warn(clippy::assert_is_empty)] #![allow(clippy::len_zero, clippy::comparison_to_empty)] fn main() { // Test with Vec let v: Vec = vec![]; assert_eq!(v, []); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty assert_ne!(v, []); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty // Test with String let s = String::new(); assert_eq!(s, ""); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty assert_ne!(s, ""); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty // Should not lint: custom message assert!(v.is_empty(), "vec is not empty"); diff --git a/tests/ui/assertions_on_collection_emptiness.rs b/tests/ui/assert_is_empty.rs similarity index 71% rename from tests/ui/assertions_on_collection_emptiness.rs rename to tests/ui/assert_is_empty.rs index ba7b0c7da321..7bc439b7176e 100644 --- a/tests/ui/assertions_on_collection_emptiness.rs +++ b/tests/ui/assert_is_empty.rs @@ -1,20 +1,20 @@ -#![warn(clippy::assertions_on_collection_emptiness)] +#![warn(clippy::assert_is_empty)] #![allow(clippy::len_zero, clippy::comparison_to_empty)] fn main() { // Test with Vec let v: Vec = vec![]; assert!(v.is_empty()); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty assert!(!v.is_empty()); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty // Test with String let s = String::new(); assert!(s.is_empty()); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty assert!(!s.is_empty()); - //~^ assertions_on_collection_emptiness + //~^ assert_is_empty // Should not lint: custom message assert!(v.is_empty(), "vec is not empty"); diff --git a/tests/ui/assertions_on_collection_emptiness.stderr b/tests/ui/assert_is_empty.stderr similarity index 68% rename from tests/ui/assertions_on_collection_emptiness.stderr rename to tests/ui/assert_is_empty.stderr index 9943c8146b84..f374d000c8a9 100644 --- a/tests/ui/assertions_on_collection_emptiness.stderr +++ b/tests/ui/assert_is_empty.stderr @@ -1,26 +1,26 @@ error: used `assert!` with an empty collection check - --> tests/ui/assertions_on_collection_emptiness.rs:7:5 + --> tests/ui/assert_is_empty.rs:7:5 | LL | assert!(v.is_empty()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_eq!(v, [])` | - = note: `-D clippy::assertions-on-collection-emptiness` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::assertions_on_collection_emptiness)]` + = note: `-D clippy::assert-is-empty` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::assert_is_empty)]` error: used `assert!` with a non-empty collection check - --> tests/ui/assertions_on_collection_emptiness.rs:9:5 + --> tests/ui/assert_is_empty.rs:9:5 | LL | assert!(!v.is_empty()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_ne!(v, [])` error: used `assert!` with an empty collection check - --> tests/ui/assertions_on_collection_emptiness.rs:14:5 + --> tests/ui/assert_is_empty.rs:14:5 | LL | assert!(s.is_empty()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_eq!(s, "")` error: used `assert!` with a non-empty collection check - --> tests/ui/assertions_on_collection_emptiness.rs:16:5 + --> tests/ui/assert_is_empty.rs:16:5 | LL | assert!(!s.is_empty()); | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `assert_ne!(s, "")` diff --git a/tests/ui/assert_is_empty_macro.rs b/tests/ui/assert_is_empty_macro.rs new file mode 100644 index 000000000000..639e7c3bd56e --- /dev/null +++ b/tests/ui/assert_is_empty_macro.rs @@ -0,0 +1,24 @@ +//@no-rustfix: macro-expanded suggestions are intentionally help-only +#![warn(clippy::assert_is_empty)] + +macro_rules! assert_empty { + ($expr:expr) => { + assert!($expr.is_empty()); + //~^ assert_is_empty + }; +} + +macro_rules! assert_not_empty { + ($expr:expr) => { + assert!(!$expr.is_empty()); + //~^ assert_is_empty + }; +} + +fn main() { + let v: Vec = vec![]; + let s = String::new(); + + assert_empty!(v); + assert_not_empty!(s); +} diff --git a/tests/ui/assert_is_empty_macro.stderr b/tests/ui/assert_is_empty_macro.stderr new file mode 100644 index 000000000000..c0fcf673b951 --- /dev/null +++ b/tests/ui/assert_is_empty_macro.stderr @@ -0,0 +1,28 @@ +error: used `assert!` with an empty collection check + --> tests/ui/assert_is_empty_macro.rs:6:9 + | +LL | assert!($expr.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | assert_empty!(v); + | ---------------- in this macro invocation + | + = help: replace with: `assert_eq!(v, [])` + = note: `-D clippy::assert-is-empty` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::assert_is_empty)]` + = note: this error originates in the macro `assert_empty` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: used `assert!` with a non-empty collection check + --> tests/ui/assert_is_empty_macro.rs:13:9 + | +LL | assert!(!$expr.is_empty()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | assert_not_empty!(s); + | -------------------- in this macro invocation + | + = help: replace with: `assert_ne!(s, "")` + = note: this error originates in the macro `assert_not_empty` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 2 previous errors + From 95fc74e3b34840f47f43cca20885bcb2a37dc101 Mon Sep 17 00:00:00 2001 From: Mihir Date: Tue, 7 Jul 2026 23:50:37 +0530 Subject: [PATCH 6/7] Update generated lint changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80748a533852..4982ad10a107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6639,7 +6639,7 @@ Released 2018-09-13 [`as_pointer_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_pointer_underscore [`as_ptr_cast_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_ptr_cast_mut [`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore -[`assertions_on_collection_emptiness`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_collection_emptiness +[`assert_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#assert_is_empty [`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants [`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states [`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern From 0470b4329b964b799b17f006e7967d8e2e5566d5 Mon Sep 17 00:00:00 2001 From: Mihir Date: Wed, 8 Jul 2026 00:03:13 +0530 Subject: [PATCH 7/7] Fix dogfood for assert is empty lint --- clippy_lints/src/assert_is_empty.rs | 16 +++------------- tests/lint_message_convention.rs | 4 ++-- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/assert_is_empty.rs b/clippy_lints/src/assert_is_empty.rs index b761c85bbed6..5d6534744c79 100644 --- a/clippy_lints/src/assert_is_empty.rs +++ b/clippy_lints/src/assert_is_empty.rs @@ -56,20 +56,10 @@ impl<'tcx> LateLintPass<'tcx> for AssertIsEmpty { ) && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) && panic_expn.is_default_message() { - let method_name; - let recv; - let is_negated; - - match condition.kind { - ExprKind::MethodCall(ms, r, [], _) => { - method_name = ms.ident.name; - recv = r; - is_negated = false; - }, + let (method_name, recv, is_negated) = match condition.kind { + ExprKind::MethodCall(ms, r, [], _) => (ms.ident.name, r, false), ExprKind::Unary(UnOp::Not, inner) if let ExprKind::MethodCall(ms, r, [], _) = inner.kind => { - method_name = ms.ident.name; - recv = r; - is_negated = true; + (ms.ident.name, r, true) }, _ => return, }; diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index 9229e2e8c496..7d8d832fba71 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -6,7 +6,7 @@ use std::sync::LazyLock; use regex::RegexSet; -#[derive(Debug)] +#[derive(Debug, PartialEq)] struct Message { path: PathBuf, bad_lines: Vec, @@ -113,5 +113,5 @@ fn lint_message_convention() { eprintln!("Check out the rustc-dev-guide for more information:"); eprintln!("https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure\n\n\n"); - assert!(bad_tests.is_empty()); + assert_eq!(bad_tests, []); }