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 rust-lang/rust-clippy#14597
This commit is contained in:
commit
69ade776fa
3 changed files with 65 additions and 12 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
28
tests/ui/len_without_is_empty_expect.rs
Normal file
28
tests/ui/len_without_is_empty_expect.rs
Normal file
|
|
@ -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() {}
|
||||
11
tests/ui/len_without_is_empty_expect.stderr
Normal file
11
tests/ui/len_without_is_empty_expect.stderr
Normal file
|
|
@ -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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue