1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_sugg, span_lint_and_then}
;
2 use clippy_utils
::source
::snippet_with_applicability
;
3 use clippy_utils
::{get_item_name, get_parent_as_impl, is_lint_allowed}
;
4 use if_chain
::if_chain
;
5 use rustc_ast
::ast
::LitKind
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::def_id
::DefIdSet
;
9 def_id
::DefId
, AssocItemKind
, BinOpKind
, Expr
, ExprKind
, FnRetTy
, ImplItem
, ImplItemKind
, ImplicitSelfKind
, Item
,
10 ItemKind
, Mutability
, Node
, TraitItemRef
, TyKind
,
12 use rustc_lint
::{LateContext, LateLintPass}
;
13 use rustc_middle
::ty
::{self, AssocKind, FnSig, Ty}
;
14 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 source_map
::{Span, Spanned, Symbol}
,
20 declare_clippy_lint
! {
22 /// Checks for getting the length of something via `.len()`
23 /// just to compare to zero, and suggests using `.is_empty()` where applicable.
25 /// ### Why is this bad?
26 /// Some structures can answer `.is_empty()` much faster
27 /// than calculating their length. So it is good to get into the habit of using
28 /// `.is_empty()`, and having it is cheap.
29 /// Besides, it makes the intent clearer than a manual comparison in some contexts.
45 /// if !y.is_empty() {
49 #[clippy::version = "pre 1.29.0"]
52 "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead"
55 declare_clippy_lint
! {
57 /// Checks for items that implement `.len()` but not
60 /// ### Why is this bad?
61 /// It is good custom to have both methods, because for
62 /// some data structures, asking about the length will be a costly operation,
63 /// whereas `.is_empty()` can usually answer in constant time. Also it used to
64 /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that
65 /// lint will ignore such entities.
70 /// pub fn len(&self) -> usize {
75 #[clippy::version = "pre 1.29.0"]
76 pub LEN_WITHOUT_IS_EMPTY
,
78 "traits or impls with a public `len` method but no corresponding `is_empty` method"
81 declare_clippy_lint
! {
83 /// Checks for comparing to an empty slice such as `""` or `[]`,
84 /// and suggests using `.is_empty()` where applicable.
86 /// ### Why is this bad?
87 /// Some structures can answer `.is_empty()` much faster
88 /// than checking for equality. So it is good to get into the habit of using
89 /// `.is_empty()`, and having it is cheap.
90 /// Besides, it makes the intent clearer than a manual comparison in some contexts.
105 /// if s.is_empty() {
109 /// if arr.is_empty() {
113 #[clippy::version = "1.49.0"]
114 pub COMPARISON_TO_EMPTY
,
116 "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
119 declare_lint_pass
!(LenZero
=> [LEN_ZERO
, LEN_WITHOUT_IS_EMPTY
, COMPARISON_TO_EMPTY
]);
121 impl<'tcx
> LateLintPass
<'tcx
> for LenZero
{
122 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
123 if item
.span
.from_expansion() {
127 if let ItemKind
::Trait(_
, _
, _
, _
, trait_items
) = item
.kind
{
128 check_trait_items(cx
, item
, trait_items
);
132 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx ImplItem
<'_
>) {
134 if item
.ident
.name
== sym
::len
;
135 if let ImplItemKind
::Fn(sig
, _
) = &item
.kind
;
136 if sig
.decl
.implicit_self
.has_implicit_self();
137 if cx
.access_levels
.is_exported(item
.def_id
);
138 if matches
!(sig
.decl
.output
, FnRetTy
::Return(_
));
139 if let Some(imp
) = get_parent_as_impl(cx
.tcx
, item
.hir_id());
140 if imp
.of_trait
.is_none();
141 if let TyKind
::Path(ty_path
) = &imp
.self_ty
.kind
;
142 if let Some(ty_id
) = cx
.qpath_res(ty_path
, imp
.self_ty
.hir_id
).opt_def_id();
143 if let Some(local_id
) = ty_id
.as_local();
144 let ty_hir_id
= cx
.tcx
.hir().local_def_id_to_hir_id(local_id
);
145 if !is_lint_allowed(cx
, LEN_WITHOUT_IS_EMPTY
, ty_hir_id
);
146 if let Some(output
) = parse_len_output(cx
, cx
.tcx
.fn_sig(item
.def_id
).skip_binder());
148 let (name
, kind
) = match cx
.tcx
.hir().find(ty_hir_id
) {
149 Some(Node
::ForeignItem(x
)) => (x
.ident
.name
, "extern type"),
150 Some(Node
::Item(x
)) => match x
.kind
{
151 ItemKind
::Struct(..) => (x
.ident
.name
, "struct"),
152 ItemKind
::Enum(..) => (x
.ident
.name
, "enum"),
153 ItemKind
::Union(..) => (x
.ident
.name
, "union"),
154 _
=> (x
.ident
.name
, "type"),
158 check_for_is_empty(cx
, sig
.span
, sig
.decl
.implicit_self
, output
, ty_id
, name
, kind
)
163 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
164 if expr
.span
.from_expansion() {
168 if let ExprKind
::Binary(Spanned { node: cmp, .. }
, left
, right
) = expr
.kind
{
171 check_cmp(cx
, expr
.span
, left
, right
, "", 0); // len == 0
172 check_cmp(cx
, expr
.span
, right
, left
, "", 0); // 0 == len
175 check_cmp(cx
, expr
.span
, left
, right
, "!", 0); // len != 0
176 check_cmp(cx
, expr
.span
, right
, left
, "!", 0); // 0 != len
179 check_cmp(cx
, expr
.span
, left
, right
, "!", 0); // len > 0
180 check_cmp(cx
, expr
.span
, right
, left
, "", 1); // 1 > len
183 check_cmp(cx
, expr
.span
, left
, right
, "", 1); // len < 1
184 check_cmp(cx
, expr
.span
, right
, left
, "!", 0); // 0 < len
186 BinOpKind
::Ge
=> check_cmp(cx
, expr
.span
, left
, right
, "!", 1), // len >= 1
187 BinOpKind
::Le
=> check_cmp(cx
, expr
.span
, right
, left
, "!", 1), // 1 <= len
194 fn check_trait_items(cx
: &LateContext
<'_
>, visited_trait
: &Item
<'_
>, trait_items
: &[TraitItemRef
]) {
195 fn is_named_self(cx
: &LateContext
<'_
>, item
: &TraitItemRef
, name
: Symbol
) -> bool
{
196 item
.ident
.name
== name
197 && if let AssocItemKind
::Fn { has_self }
= item
.kind
{
198 has_self
&& { cx.tcx.fn_sig(item.id.def_id).inputs().skip_binder().len() == 1 }
204 // fill the set with current and super traits
205 fn fill_trait_set(traitt
: DefId
, set
: &mut DefIdSet
, cx
: &LateContext
<'_
>) {
206 if set
.insert(traitt
) {
207 for supertrait
in rustc_trait_selection
::traits
::supertrait_def_ids(cx
.tcx
, traitt
) {
208 fill_trait_set(supertrait
, set
, cx
);
213 if cx
.access_levels
.is_exported(visited_trait
.def_id
) && trait_items
.iter().any(|i
| is_named_self(cx
, i
, sym
::len
))
215 let mut current_and_super_traits
= DefIdSet
::default();
216 fill_trait_set(visited_trait
.def_id
.to_def_id(), &mut current_and_super_traits
, cx
);
217 let is_empty
= sym
!(is_empty
);
219 let is_empty_method_found
= current_and_super_traits
221 .flat_map(|&i
| cx
.tcx
.associated_items(i
).filter_by_name_unhygienic(is_empty
))
223 i
.kind
== ty
::AssocKind
::Fn
224 && i
.fn_has_self_parameter
225 && cx
.tcx
.fn_sig(i
.def_id
).inputs().skip_binder().len() == 1
228 if !is_empty_method_found
{
231 LEN_WITHOUT_IS_EMPTY
,
234 "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
235 visited_trait
.ident
.name
242 #[derive(Debug, Clone, Copy)]
243 enum LenOutput
<'tcx
> {
246 Result(DefId
, Ty
<'tcx
>),
248 fn parse_len_output
<'tcx
>(cx
: &LateContext
<'_
>, sig
: FnSig
<'tcx
>) -> Option
<LenOutput
<'tcx
>> {
249 match *sig
.output().kind() {
250 ty
::Int(_
) | ty
::Uint(_
) => Some(LenOutput
::Integral
),
251 ty
::Adt(adt
, subs
) if cx
.tcx
.is_diagnostic_item(sym
::Option
, adt
.did()) => {
252 subs
.type_at(0).is_integral().then(|| LenOutput
::Option(adt
.did()))
254 ty
::Adt(adt
, subs
) if cx
.tcx
.is_diagnostic_item(sym
::Result
, adt
.did()) => subs
257 .then(|| LenOutput
::Result(adt
.did(), subs
.type_at(1))),
263 fn matches_is_empty_output(self, ty
: Ty
<'_
>) -> bool
{
264 match (self, ty
.kind()) {
265 (_
, &ty
::Bool
) => true,
266 (Self::Option(id
), &ty
::Adt(adt
, subs
)) if id
== adt
.did() => subs
.type_at(0).is_bool(),
267 (Self::Result(id
, err_ty
), &ty
::Adt(adt
, subs
)) if id
== adt
.did() => {
268 subs
.type_at(0).is_bool() && subs
.type_at(1) == err_ty
274 fn expected_sig(self, self_kind
: ImplicitSelfKind
) -> String
{
275 let self_ref
= match self_kind
{
276 ImplicitSelfKind
::ImmRef
=> "&",
277 ImplicitSelfKind
::MutRef
=> "&mut ",
281 Self::Integral
=> format
!("expected signature: `({}self) -> bool`", self_ref
),
282 Self::Option(_
) => format
!(
283 "expected signature: `({}self) -> bool` or `({}self) -> Option<bool>",
286 Self::Result(..) => format
!(
287 "expected signature: `({}self) -> bool` or `({}self) -> Result<bool>",
294 /// Checks if the given signature matches the expectations for `is_empty`
295 fn check_is_empty_sig(sig
: FnSig
<'_
>, self_kind
: ImplicitSelfKind
, len_output
: LenOutput
<'_
>) -> bool
{
296 match &**sig
.inputs_and_output
{
297 [arg
, res
] if len_output
.matches_is_empty_output(*res
) => {
299 (arg
.kind(), self_kind
),
300 (ty
::Ref(_
, _
, Mutability
::Not
), ImplicitSelfKind
::ImmRef
)
301 | (ty
::Ref(_
, _
, Mutability
::Mut
), ImplicitSelfKind
::MutRef
)
302 ) || (!arg
.is_ref() && matches
!(self_kind
, ImplicitSelfKind
::Imm
| ImplicitSelfKind
::Mut
))
308 /// Checks if the given type has an `is_empty` method with the appropriate signature.
309 fn check_for_is_empty(
310 cx
: &LateContext
<'_
>,
312 self_kind
: ImplicitSelfKind
,
313 output
: LenOutput
<'_
>,
318 let is_empty
= Symbol
::intern("is_empty");
321 .inherent_impls(impl_ty
)
323 .flat_map(|&id
| cx
.tcx
.associated_items(id
).filter_by_name_unhygienic(is_empty
))
324 .find(|item
| item
.kind
== AssocKind
::Fn
);
326 let (msg
, is_empty_span
, self_kind
) = match is_empty
{
329 "{} `{}` has a public `len` method, but no `is_empty` method",
336 Some(is_empty
) if !cx
.access_levels
.is_exported(is_empty
.def_id
.expect_local()) => (
338 "{} `{}` has a public `len` method, but a private `is_empty` method",
342 Some(cx
.tcx
.def_span(is_empty
.def_id
)),
346 if !(is_empty
.fn_has_self_parameter
347 && check_is_empty_sig(cx
.tcx
.fn_sig(is_empty
.def_id
).skip_binder(), self_kind
, output
)) =>
351 "{} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
355 Some(cx
.tcx
.def_span(is_empty
.def_id
)),
362 span_lint_and_then(cx
, LEN_WITHOUT_IS_EMPTY
, span
, &msg
, |db
| {
363 if let Some(span
) = is_empty_span
{
364 db
.span_note(span
, "`is_empty` defined here");
366 if let Some(self_kind
) = self_kind
{
367 db
.note(&output
.expected_sig(self_kind
));
372 fn check_cmp(cx
: &LateContext
<'_
>, span
: Span
, method
: &Expr
<'_
>, lit
: &Expr
<'_
>, op
: &str, compare_to
: u32) {
373 if let (&ExprKind
::MethodCall(method_path
, args
, _
), &ExprKind
::Lit(ref lit
)) = (&method
.kind
, &lit
.kind
) {
374 // check if we are in an is_empty() method
375 if let Some(name
) = get_item_name(cx
, method
) {
376 if name
.as_str() == "is_empty" {
381 check_len(cx
, span
, method_path
.ident
.name
, args
, &lit
.node
, op
, compare_to
);
383 check_empty_expr(cx
, span
, method
, lit
, op
);
388 cx
: &LateContext
<'_
>,
396 if let LitKind
::Int(lit
, _
) = *lit
{
397 // check if length is compared to the specified number
398 if lit
!= u128
::from(compare_to
) {
402 if method_name
== sym
::len
&& args
.len() == 1 && has_is_empty(cx
, &args
[0]) {
403 let mut applicability
= Applicability
::MachineApplicable
;
408 &format
!("length comparison to {}", if compare_to
== 0 { "zero" }
else { "one" }
),
409 &format
!("using `{}is_empty` is clearer and more explicit", op
),
413 snippet_with_applicability(cx
, args
[0].span
, "_", &mut applicability
)
421 fn check_empty_expr(cx
: &LateContext
<'_
>, span
: Span
, lit1
: &Expr
<'_
>, lit2
: &Expr
<'_
>, op
: &str) {
422 if (is_empty_array(lit2
) || is_empty_string(lit2
)) && has_is_empty(cx
, lit1
) {
423 let mut applicability
= Applicability
::MachineApplicable
;
428 "comparison to empty slice",
429 &format
!("using `{}is_empty` is clearer and more explicit", op
),
433 snippet_with_applicability(cx
, lit1
.span
, "_", &mut applicability
)
440 fn is_empty_string(expr
: &Expr
<'_
>) -> bool
{
441 if let ExprKind
::Lit(ref lit
) = expr
.kind
{
442 if let LitKind
::Str(lit
, _
) = lit
.node
{
443 let lit
= lit
.as_str();
444 return lit
.is_empty();
450 fn is_empty_array(expr
: &Expr
<'_
>) -> bool
{
451 if let ExprKind
::Array(arr
) = expr
.kind
{
452 return arr
.is_empty();
457 /// Checks if this type has an `is_empty` method.
458 fn has_is_empty(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
459 /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
460 fn is_is_empty(cx
: &LateContext
<'_
>, item
: &ty
::AssocItem
) -> bool
{
461 if item
.kind
== ty
::AssocKind
::Fn
{
462 let sig
= cx
.tcx
.fn_sig(item
.def_id
);
463 let ty
= sig
.skip_binder();
464 ty
.inputs().len() == 1
470 /// Checks the inherent impl's items for an `is_empty(self)` method.
471 fn has_is_empty_impl(cx
: &LateContext
<'_
>, id
: DefId
) -> bool
{
472 let is_empty
= sym
!(is_empty
);
473 cx
.tcx
.inherent_impls(id
).iter().any(|imp
| {
475 .associated_items(*imp
)
476 .filter_by_name_unhygienic(is_empty
)
477 .any(|item
| is_is_empty(cx
, item
))
481 let ty
= &cx
.typeck_results().expr_ty(expr
).peel_refs();
483 ty
::Dynamic(tt
, ..) => tt
.principal().map_or(false, |principal
| {
484 let is_empty
= sym
!(is_empty
);
486 .associated_items(principal
.def_id())
487 .filter_by_name_unhygienic(is_empty
)
488 .any(|item
| is_is_empty(cx
, item
))
490 ty
::Projection(ref proj
) => has_is_empty_impl(cx
, proj
.item_def_id
),
491 ty
::Adt(id
, _
) => has_is_empty_impl(cx
, id
.did()),
492 ty
::Array(..) | ty
::Slice(..) | ty
::Str
=> true,