about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2025-04-13 15:52:38 +0200
committerSamuel Tardieu <sam@rfc1149.net>2025-04-13 22:04:15 +0200
commit327da45aee7a973d0f14c59e7ed30511abda86e5 (patch)
treee8c9f20ed37cbc8459785df0e8540a70cc570ef3
parentd19c651c627a69db728c76a7647853a44b28160c (diff)
downloadrust-327da45aee7a973d0f14c59e7ed30511abda86e5.tar.gz
rust-327da45aee7a973d0f14c59e7ed30511abda86e5.zip
Fulfill the lint expectation even if allowed at the type level
`clippy::len_without_is_empty` can be allowed at the type declaration
site, and this will prevent the lint from triggering even though the
lint is shown on the `len()` method definition.

This allows the lint to be expected even though it is allowed at the
type declaration site.
-rw-r--r--clippy_lints/src/len_zero.rs38
-rw-r--r--tests/ui/len_without_is_empty_expect.rs28
-rw-r--r--tests/ui/len_without_is_empty_expect.stderr11
3 files changed, 65 insertions, 12 deletions
diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs
index b38738524cd..18bcf634097 100644
--- a/clippy_lints/src/len_zero.rs
+++ b/clippy_lints/src/len_zero.rs
@@ -2,13 +2,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
 use clippy_utils::source::{SpanRangeExt, snippet_with_context};
 use clippy_utils::sugg::{Sugg, has_enclosing_paren};
 use clippy_utils::ty::implements_trait;
-use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, is_trait_method, peel_ref_operators};
+use clippy_utils::{fulfill_or_allowed, get_item_name, get_parent_as_impl, is_trait_method, peel_ref_operators};
 use rustc_ast::ast::LitKind;
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
 use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_hir::{
-    AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind,
+    AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind,
     ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy,
     QPath, TraitItemRef, TyKind,
 };
@@ -143,7 +143,6 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
             && let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
             && let Some(local_id) = ty_id.as_local()
             && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
-            && !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id)
             && let Some(output) =
                 parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
         {
@@ -157,7 +156,17 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
                 },
                 _ => return,
             };
-            check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
+            check_for_is_empty(
+                cx,
+                sig.span,
+                sig.decl.implicit_self,
+                output,
+                ty_id,
+                name,
+                kind,
+                item.hir_id(),
+                ty_hir_id,
+            );
         }
     }
 
@@ -447,6 +456,7 @@ fn check_is_empty_sig<'tcx>(
 }
 
 /// Checks if the given type has an `is_empty` method with the appropriate signature.
+#[expect(clippy::too_many_arguments)]
 fn check_for_is_empty(
     cx: &LateContext<'_>,
     span: Span,
@@ -455,6 +465,8 @@ fn check_for_is_empty(
     impl_ty: DefId,
     item_name: Symbol,
     item_kind: &str,
+    len_method_hir_id: HirId,
+    ty_decl_hir_id: HirId,
 ) {
     // Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
     // find the correct inherent impls.
@@ -510,14 +522,16 @@ fn check_for_is_empty(
         Some(_) => return,
     };
 
-    span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
-        if let Some(span) = is_empty_span {
-            db.span_note(span, "`is_empty` defined here");
-        }
-        if let Some(self_kind) = self_kind {
-            db.note(output.expected_sig(self_kind));
-        }
-    });
+    if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
+        span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
+            if let Some(span) = is_empty_span {
+                db.span_note(span, "`is_empty` defined here");
+            }
+            if let Some(self_kind) = self_kind {
+                db.note(output.expected_sig(self_kind));
+            }
+        });
+    }
 }
 
 fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
diff --git a/tests/ui/len_without_is_empty_expect.rs b/tests/ui/len_without_is_empty_expect.rs
new file mode 100644
index 00000000000..9d1245e2d02
--- /dev/null
+++ b/tests/ui/len_without_is_empty_expect.rs
@@ -0,0 +1,28 @@
+//@no-rustfix
+#![allow(clippy::len_without_is_empty)]
+
+// Check that the lint expectation is fulfilled even if the lint is allowed at the type level.
+pub struct Empty;
+
+impl Empty {
+    #[expect(clippy::len_without_is_empty)]
+    pub fn len(&self) -> usize {
+        0
+    }
+}
+
+// Check that the lint expectation is not triggered if it should not
+pub struct Empty2;
+
+impl Empty2 {
+    #[expect(clippy::len_without_is_empty)] //~ ERROR: this lint expectation is unfulfilled
+    pub fn len(&self) -> usize {
+        0
+    }
+
+    pub fn is_empty(&self) -> bool {
+        false
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/len_without_is_empty_expect.stderr b/tests/ui/len_without_is_empty_expect.stderr
new file mode 100644
index 00000000000..e96870f054e
--- /dev/null
+++ b/tests/ui/len_without_is_empty_expect.stderr
@@ -0,0 +1,11 @@
+error: this lint expectation is unfulfilled
+  --> tests/ui/len_without_is_empty_expect.rs:18:14
+   |
+LL |     #[expect(clippy::len_without_is_empty)]
+   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D unfulfilled-lint-expectations` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]`
+
+error: aborting due to 1 previous error
+