Skip to content

Commit 47145de

Browse files
committed
len_without_is_empty will now consider multiple impl blocks
`len_without_is_empty` will now consider `#[allow]` on both the `len` method, and the type definition
1 parent 5945e85 commit 47145de

File tree

4 files changed

+231
-77
lines changed

4 files changed

+231
-77
lines changed

clippy_lints/src/len_zero.rs

Lines changed: 125 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
use crate::utils::{get_item_name, snippet_with_applicability, span_lint, span_lint_and_sugg};
1+
use crate::utils::{
2+
get_item_name, get_parent_as_impl, is_allowed, snippet_with_applicability, span_lint, span_lint_and_sugg,
3+
span_lint_and_then,
4+
};
5+
use if_chain::if_chain;
26
use rustc_ast::ast::LitKind;
37
use rustc_data_structures::fx::FxHashSet;
48
use rustc_errors::Applicability;
5-
use rustc_hir::def_id::DefId;
6-
use rustc_hir::{AssocItemKind, BinOpKind, Expr, ExprKind, Impl, ImplItemRef, Item, ItemKind, TraitItemRef};
9+
use rustc_hir::{
10+
def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
11+
ItemKind, Mutability, Node, TraitItemRef, TyKind,
12+
};
713
use rustc_lint::{LateContext, LateLintPass};
8-
use rustc_middle::ty;
14+
use rustc_middle::ty::{self, AssocKind, FnSig};
915
use rustc_session::{declare_lint_pass, declare_tool_lint};
1016
use rustc_span::source_map::{Span, Spanned, Symbol};
1117

@@ -113,14 +119,38 @@ impl<'tcx> LateLintPass<'tcx> for LenZero {
113119
return;
114120
}
115121

116-
match item.kind {
117-
ItemKind::Trait(_, _, _, _, ref trait_items) => check_trait_items(cx, item, trait_items),
118-
ItemKind::Impl(Impl {
119-
of_trait: None,
120-
items: ref impl_items,
121-
..
122-
}) => check_impl_items(cx, item, impl_items),
123-
_ => (),
122+
if let ItemKind::Trait(_, _, _, _, ref trait_items) = item.kind {
123+
check_trait_items(cx, item, trait_items);
124+
}
125+
}
126+
127+
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
128+
if_chain! {
129+
if item.ident.as_str() == "len";
130+
if let ImplItemKind::Fn(sig, _) = &item.kind;
131+
if sig.decl.implicit_self.has_implicit_self();
132+
if cx.access_levels.is_exported(item.hir_id());
133+
if matches!(sig.decl.output, FnRetTy::Return(_));
134+
if let Some(imp) = get_parent_as_impl(cx.tcx, item.hir_id());
135+
if imp.of_trait.is_none();
136+
if let TyKind::Path(ty_path) = &imp.self_ty.kind;
137+
if let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id();
138+
if let Some(local_id) = ty_id.as_local();
139+
let ty_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id);
140+
if !is_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id);
141+
then {
142+
let (name, kind) = match cx.tcx.hir().find(ty_hir_id) {
143+
Some(Node::ForeignItem(x)) => (x.ident.name, "extern type"),
144+
Some(Node::Item(x)) => match x.kind {
145+
ItemKind::Struct(..) => (x.ident.name, "struct"),
146+
ItemKind::Enum(..) => (x.ident.name, "enum"),
147+
ItemKind::Union(..) => (x.ident.name, "union"),
148+
_ => (x.ident.name, "type"),
149+
}
150+
_ => return,
151+
};
152+
check_for_is_empty(cx, sig.span, sig.decl.implicit_self, ty_id, name, kind)
153+
}
124154
}
125155
}
126156

@@ -202,40 +232,94 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
202232
}
203233
}
204234

205-
fn check_impl_items(cx: &LateContext<'_>, item: &Item<'_>, impl_items: &[ImplItemRef<'_>]) {
206-
fn is_named_self(cx: &LateContext<'_>, item: &ImplItemRef<'_>, name: &str) -> bool {
207-
item.ident.name.as_str() == name
208-
&& if let AssocItemKind::Fn { has_self } = item.kind {
209-
has_self && cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1
210-
} else {
211-
false
212-
}
235+
/// Checks if the given signature matches the expectations for `is_empty`
236+
fn check_is_empty_sig(cx: &LateContext<'_>, sig: FnSig<'_>, self_kind: ImplicitSelfKind) -> bool {
237+
match &**sig.inputs_and_output {
238+
[arg, res] if *res == cx.tcx.types.bool => {
239+
matches!(
240+
(arg.kind(), self_kind),
241+
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
242+
| (ty::Ref(_, _, Mutability::Mut), ImplicitSelfKind::MutRef)
243+
) || (!arg.is_ref() && matches!(self_kind, ImplicitSelfKind::Imm | ImplicitSelfKind::Mut))
244+
},
245+
_ => false,
213246
}
247+
}
214248

215-
let is_empty = if let Some(is_empty) = impl_items.iter().find(|i| is_named_self(cx, i, "is_empty")) {
216-
if cx.access_levels.is_exported(is_empty.id.hir_id()) {
217-
return;
218-
}
219-
"a private"
220-
} else {
221-
"no corresponding"
222-
};
223-
224-
if let Some(i) = impl_items.iter().find(|i| is_named_self(cx, i, "len")) {
225-
if cx.access_levels.is_exported(i.id.hir_id()) {
226-
let ty = cx.tcx.type_of(item.def_id);
249+
/// Checks if the given type has an `is_empty` method with the appropriate signature.
250+
fn check_for_is_empty(
251+
cx: &LateContext<'_>,
252+
span: Span,
253+
self_kind: ImplicitSelfKind,
254+
impl_ty: DefId,
255+
item_name: Symbol,
256+
item_kind: &str,
257+
) {
258+
let is_empty = Symbol::intern("is_empty");
259+
let is_empty = cx
260+
.tcx
261+
.inherent_impls(impl_ty)
262+
.iter()
263+
.flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
264+
.find(|item| item.kind == AssocKind::Fn);
227265

228-
span_lint(
229-
cx,
230-
LEN_WITHOUT_IS_EMPTY,
231-
item.span,
232-
&format!(
233-
"item `{}` has a public `len` method but {} `is_empty` method",
234-
ty, is_empty
266+
let (msg, is_empty_span, self_kind) = match is_empty {
267+
None => (
268+
format!(
269+
"{} `{}` has a public `len` method, but no `is_empty` method",
270+
item_kind,
271+
item_name.as_str(),
272+
),
273+
None,
274+
None,
275+
),
276+
Some(is_empty)
277+
if !cx
278+
.access_levels
279+
.is_exported(cx.tcx.hir().local_def_id_to_hir_id(is_empty.def_id.expect_local())) =>
280+
{
281+
(
282+
format!(
283+
"{} `{}` has a public `len` method, but a private `is_empty` method",
284+
item_kind,
285+
item_name.as_str(),
235286
),
236-
);
287+
Some(cx.tcx.def_span(is_empty.def_id)),
288+
None,
289+
)
290+
},
291+
Some(is_empty)
292+
if !(is_empty.fn_has_self_parameter
293+
&& check_is_empty_sig(cx, cx.tcx.fn_sig(is_empty.def_id).skip_binder(), self_kind)) =>
294+
{
295+
(
296+
format!(
297+
"{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
298+
item_kind,
299+
item_name.as_str(),
300+
),
301+
Some(cx.tcx.def_span(is_empty.def_id)),
302+
Some(self_kind),
303+
)
304+
},
305+
Some(_) => return,
306+
};
307+
308+
span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, &msg, |db| {
309+
if let Some(span) = is_empty_span {
310+
db.span_note(span, "`is_empty` defined here");
237311
}
238-
}
312+
if let Some(self_kind) = self_kind {
313+
db.note(&format!(
314+
"expected signature: `({}self) -> bool`",
315+
match self_kind {
316+
ImplicitSelfKind::ImmRef => "&",
317+
ImplicitSelfKind::MutRef => "&mut ",
318+
_ => "",
319+
}
320+
));
321+
}
322+
});
239323
}
240324

241325
fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) {

clippy_utils/src/lib.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE};
6363
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
6464
use rustc_hir::Node;
6565
use rustc_hir::{
66-
def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, GenericArgs, HirId, ImplItem, ImplItemKind, Item,
67-
ItemKind, MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef, TyKind,
68-
Unsafety,
66+
def, Arm, Block, Body, Constness, Crate, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
67+
Item, ItemKind, MatchSource, Param, Pat, PatKind, Path, PathSegment, QPath, TraitItem, TraitItemKind, TraitRef,
68+
TyKind, Unsafety,
6969
};
7070
use rustc_infer::infer::TyCtxtInferExt;
7171
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -1004,6 +1004,21 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio
10041004
})
10051005
}
10061006

1007+
/// Gets the parent node if it's an impl block.
1008+
pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> {
1009+
let map = tcx.hir();
1010+
match map.parent_iter(id).next() {
1011+
Some((
1012+
_,
1013+
Node::Item(Item {
1014+
kind: ItemKind::Impl(imp),
1015+
..
1016+
}),
1017+
)) => Some(imp),
1018+
_ => None,
1019+
}
1020+
}
1021+
10071022
/// Returns the base type for HIR references and pointers.
10081023
pub fn walk_ptrs_hir_ty<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
10091024
match ty.kind {

tests/ui/len_without_is_empty.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ impl PubAllowed {
3434
}
3535
}
3636

37+
pub struct PubAllowedFn;
38+
39+
impl PubAllowedFn {
40+
#[allow(clippy::len_without_is_empty)]
41+
pub fn len(&self) -> isize {
42+
1
43+
}
44+
}
45+
46+
#[allow(clippy::len_without_is_empty)]
47+
pub struct PubAllowedStruct;
48+
49+
impl PubAllowedStruct {
50+
pub fn len(&self) -> isize {
51+
1
52+
}
53+
}
54+
3755
pub trait PubTraitsToo {
3856
fn len(&self) -> isize;
3957
}
@@ -68,6 +86,18 @@ impl HasWrongIsEmpty {
6886
}
6987
}
7088

89+
pub struct MismatchedSelf;
90+
91+
impl MismatchedSelf {
92+
pub fn len(self) -> isize {
93+
1
94+
}
95+
96+
pub fn is_empty(&self) -> bool {
97+
false
98+
}
99+
}
100+
71101
struct NotPubOne;
72102

73103
impl NotPubOne {
@@ -142,4 +172,19 @@ pub trait DependsOnFoo: Foo {
142172
fn len(&mut self) -> usize;
143173
}
144174

175+
pub struct MultipleImpls;
176+
177+
// issue #1562
178+
impl MultipleImpls {
179+
pub fn len(&self) -> usize {
180+
1
181+
}
182+
}
183+
184+
impl MultipleImpls {
185+
pub fn is_empty(&self) -> bool {
186+
false
187+
}
188+
}
189+
145190
fn main() {}

tests/ui/len_without_is_empty.stderr

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,64 @@
1-
error: item `PubOne` has a public `len` method but no corresponding `is_empty` method
2-
--> $DIR/len_without_is_empty.rs:6:1
1+
error: struct `PubOne` has a public `len` method, but no `is_empty` method
2+
--> $DIR/len_without_is_empty.rs:7:5
33
|
4-
LL | / impl PubOne {
5-
LL | | pub fn len(&self) -> isize {
6-
LL | | 1
7-
LL | | }
8-
LL | | }
9-
| |_^
4+
LL | pub fn len(&self) -> isize {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
106
|
117
= note: `-D clippy::len-without-is-empty` implied by `-D warnings`
128

139
error: trait `PubTraitsToo` has a `len` method but no (possibly inherited) `is_empty` method
14-
--> $DIR/len_without_is_empty.rs:37:1
10+
--> $DIR/len_without_is_empty.rs:55:1
1511
|
1612
LL | / pub trait PubTraitsToo {
1713
LL | | fn len(&self) -> isize;
1814
LL | | }
1915
| |_^
2016

21-
error: item `HasIsEmpty` has a public `len` method but a private `is_empty` method
22-
--> $DIR/len_without_is_empty.rs:49:1
23-
|
24-
LL | / impl HasIsEmpty {
25-
LL | | pub fn len(&self) -> isize {
26-
LL | | 1
27-
LL | | }
28-
... |
29-
LL | | }
30-
LL | | }
31-
| |_^
17+
error: struct `HasIsEmpty` has a public `len` method, but a private `is_empty` method
18+
--> $DIR/len_without_is_empty.rs:68:5
19+
|
20+
LL | pub fn len(&self) -> isize {
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
22+
|
23+
note: `is_empty` defined here
24+
--> $DIR/len_without_is_empty.rs:72:5
25+
|
26+
LL | fn is_empty(&self) -> bool {
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
3228

33-
error: item `HasWrongIsEmpty` has a public `len` method but no corresponding `is_empty` method
34-
--> $DIR/len_without_is_empty.rs:61:1
35-
|
36-
LL | / impl HasWrongIsEmpty {
37-
LL | | pub fn len(&self) -> isize {
38-
LL | | 1
39-
LL | | }
40-
... |
41-
LL | | }
42-
LL | | }
43-
| |_^
29+
error: struct `HasWrongIsEmpty` has a public `len` method, but the `is_empty` method has an unexpected signature
30+
--> $DIR/len_without_is_empty.rs:80:5
31+
|
32+
LL | pub fn len(&self) -> isize {
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
|
35+
note: `is_empty` defined here
36+
--> $DIR/len_without_is_empty.rs:84:5
37+
|
38+
LL | pub fn is_empty(&self, x: u32) -> bool {
39+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40+
= note: expected signature: `(&self) -> bool`
41+
42+
error: struct `MismatchedSelf` has a public `len` method, but the `is_empty` method has an unexpected signature
43+
--> $DIR/len_without_is_empty.rs:92:5
44+
|
45+
LL | pub fn len(self) -> isize {
46+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
47+
|
48+
note: `is_empty` defined here
49+
--> $DIR/len_without_is_empty.rs:96:5
50+
|
51+
LL | pub fn is_empty(&self) -> bool {
52+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53+
= note: expected signature: `(self) -> bool`
4454

4555
error: trait `DependsOnFoo` has a `len` method but no (possibly inherited) `is_empty` method
46-
--> $DIR/len_without_is_empty.rs:141:1
56+
--> $DIR/len_without_is_empty.rs:171:1
4757
|
4858
LL | / pub trait DependsOnFoo: Foo {
4959
LL | | fn len(&mut self) -> usize;
5060
LL | | }
5161
| |_^
5262

53-
error: aborting due to 5 previous errors
63+
error: aborting due to 6 previous errors
5464

0 commit comments

Comments
 (0)