Skip to content

Commit 69ade77

Browse files
authored
Fulfill the lint expectation even if allowed at the type level (#14604)
`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. changelog: [`len_without_is_empty`]: the lint can now be `#[expect]`ed on the `len()` method even when it is `#[allow]`ed on the definining type. Fixes #14597
2 parents b90c80a + 327da45 commit 69ade77

File tree

3 files changed

+65
-12
lines changed

3 files changed

+65
-12
lines changed

clippy_lints/src/len_zero.rs

+26-12
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the
22
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
33
use clippy_utils::sugg::{Sugg, has_enclosing_paren};
44
use clippy_utils::ty::implements_trait;
5-
use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, is_trait_method, peel_ref_operators};
5+
use clippy_utils::{fulfill_or_allowed, get_item_name, get_parent_as_impl, is_trait_method, peel_ref_operators};
66
use rustc_ast::ast::LitKind;
77
use rustc_errors::Applicability;
88
use rustc_hir::def::Res;
99
use rustc_hir::def_id::{DefId, DefIdSet};
1010
use rustc_hir::{
11-
AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind,
11+
AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind,
1212
ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy,
1313
QPath, TraitItemRef, TyKind,
1414
};
@@ -143,7 +143,6 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
143143
&& let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id()
144144
&& let Some(local_id) = ty_id.as_local()
145145
&& let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id)
146-
&& !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id)
147146
&& let Some(output) =
148147
parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder())
149148
{
@@ -157,7 +156,17 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
157156
},
158157
_ => return,
159158
};
160-
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind);
159+
check_for_is_empty(
160+
cx,
161+
sig.span,
162+
sig.decl.implicit_self,
163+
output,
164+
ty_id,
165+
name,
166+
kind,
167+
item.hir_id(),
168+
ty_hir_id,
169+
);
161170
}
162171
}
163172

@@ -447,6 +456,7 @@ fn check_is_empty_sig<'tcx>(
447456
}
448457

449458
/// Checks if the given type has an `is_empty` method with the appropriate signature.
459+
#[expect(clippy::too_many_arguments)]
450460
fn check_for_is_empty(
451461
cx: &LateContext<'_>,
452462
span: Span,
@@ -455,6 +465,8 @@ fn check_for_is_empty(
455465
impl_ty: DefId,
456466
item_name: Symbol,
457467
item_kind: &str,
468+
len_method_hir_id: HirId,
469+
ty_decl_hir_id: HirId,
458470
) {
459471
// Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to
460472
// find the correct inherent impls.
@@ -510,14 +522,16 @@ fn check_for_is_empty(
510522
Some(_) => return,
511523
};
512524

513-
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
514-
if let Some(span) = is_empty_span {
515-
db.span_note(span, "`is_empty` defined here");
516-
}
517-
if let Some(self_kind) = self_kind {
518-
db.note(output.expected_sig(self_kind));
519-
}
520-
});
525+
if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) {
526+
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| {
527+
if let Some(span) = is_empty_span {
528+
db.span_note(span, "`is_empty` defined here");
529+
}
530+
if let Some(self_kind) = self_kind {
531+
db.note(output.expected_sig(self_kind));
532+
}
533+
});
534+
}
521535
}
522536

523537
fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//@no-rustfix
2+
#![allow(clippy::len_without_is_empty)]
3+
4+
// Check that the lint expectation is fulfilled even if the lint is allowed at the type level.
5+
pub struct Empty;
6+
7+
impl Empty {
8+
#[expect(clippy::len_without_is_empty)]
9+
pub fn len(&self) -> usize {
10+
0
11+
}
12+
}
13+
14+
// Check that the lint expectation is not triggered if it should not
15+
pub struct Empty2;
16+
17+
impl Empty2 {
18+
#[expect(clippy::len_without_is_empty)] //~ ERROR: this lint expectation is unfulfilled
19+
pub fn len(&self) -> usize {
20+
0
21+
}
22+
23+
pub fn is_empty(&self) -> bool {
24+
false
25+
}
26+
}
27+
28+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: this lint expectation is unfulfilled
2+
--> tests/ui/len_without_is_empty_expect.rs:18:14
3+
|
4+
LL | #[expect(clippy::len_without_is_empty)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `-D unfulfilled-lint-expectations` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(unfulfilled_lint_expectations)]`
9+
10+
error: aborting due to 1 previous error
11+

0 commit comments

Comments
 (0)