1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_sugg, span_lint_and_then}
;
2 use clippy_utils
::source
::snippet_with_context
;
3 use clippy_utils
::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators, sugg::Sugg}
;
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
::Res
, def_id
::DefId
, lang_items
::LangItem
, AssocItemKind
, BinOpKind
, Expr
, ExprKind
, FnRetTy
, GenericArg
,
10 GenericBound
, ImplItem
, ImplItemKind
, ImplicitSelfKind
, Item
, ItemKind
, Mutability
, Node
, PathSegment
, PrimTy
,
11 QPath
, TraitItemRef
, TyKind
, TypeBindingKind
,
13 use rustc_lint
::{LateContext, LateLintPass}
;
14 use rustc_middle
::ty
::{self, AssocKind, FnSig, Ty}
;
15 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
17 source_map
::{Span, Spanned, Symbol}
,
21 declare_clippy_lint
! {
23 /// Checks for getting the length of something via `.len()`
24 /// just to compare to zero, and suggests using `.is_empty()` where applicable.
26 /// ### Why is this bad?
27 /// Some structures can answer `.is_empty()` much faster
28 /// than calculating their length. So it is good to get into the habit of using
29 /// `.is_empty()`, and having it is cheap.
30 /// Besides, it makes the intent clearer than a manual comparison in some contexts.
46 /// if !y.is_empty() {
50 #[clippy::version = "pre 1.29.0"]
53 "checking `.len() == 0` or `.len() > 0` (or similar) when `.is_empty()` could be used instead"
56 declare_clippy_lint
! {
58 /// Checks for items that implement `.len()` but not
61 /// ### Why is this bad?
62 /// It is good custom to have both methods, because for
63 /// some data structures, asking about the length will be a costly operation,
64 /// whereas `.is_empty()` can usually answer in constant time. Also it used to
65 /// lead to false positives on the [`len_zero`](#len_zero) lint – currently that
66 /// lint will ignore such entities.
71 /// pub fn len(&self) -> usize {
76 #[clippy::version = "pre 1.29.0"]
77 pub LEN_WITHOUT_IS_EMPTY
,
79 "traits or impls with a public `len` method but no corresponding `is_empty` method"
82 declare_clippy_lint
! {
84 /// Checks for comparing to an empty slice such as `""` or `[]`,
85 /// and suggests using `.is_empty()` where applicable.
87 /// ### Why is this bad?
88 /// Some structures can answer `.is_empty()` much faster
89 /// than checking for equality. So it is good to get into the habit of using
90 /// `.is_empty()`, and having it is cheap.
91 /// Besides, it makes the intent clearer than a manual comparison in some contexts.
106 /// if s.is_empty() {
110 /// if arr.is_empty() {
114 #[clippy::version = "1.49.0"]
115 pub COMPARISON_TO_EMPTY
,
117 "checking `x == \"\"` or `x == []` (or similar) when `.is_empty()` could be used instead"
120 declare_lint_pass
!(LenZero
=> [LEN_ZERO
, LEN_WITHOUT_IS_EMPTY
, COMPARISON_TO_EMPTY
]);
122 impl<'tcx
> LateLintPass
<'tcx
> for LenZero
{
123 fn check_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx Item
<'_
>) {
124 if item
.span
.from_expansion() {
128 if let ItemKind
::Trait(_
, _
, _
, _
, trait_items
) = item
.kind
{
129 check_trait_items(cx
, item
, trait_items
);
133 fn check_impl_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx ImplItem
<'_
>) {
135 if item
.ident
.name
== sym
::len
;
136 if let ImplItemKind
::Fn(sig
, _
) = &item
.kind
;
137 if sig
.decl
.implicit_self
.has_implicit_self();
138 if sig
.decl
.inputs
.len() == 1;
139 if cx
.effective_visibilities
.is_exported(item
.owner_id
.def_id
);
140 if matches
!(sig
.decl
.output
, FnRetTy
::Return(_
));
141 if let Some(imp
) = get_parent_as_impl(cx
.tcx
, item
.hir_id());
142 if imp
.of_trait
.is_none();
143 if let TyKind
::Path(ty_path
) = &imp
.self_ty
.kind
;
144 if let Some(ty_id
) = cx
.qpath_res(ty_path
, imp
.self_ty
.hir_id
).opt_def_id();
145 if let Some(local_id
) = ty_id
.as_local();
146 let ty_hir_id
= cx
.tcx
.hir().local_def_id_to_hir_id(local_id
);
147 if !is_lint_allowed(cx
, LEN_WITHOUT_IS_EMPTY
, ty_hir_id
);
148 if let Some(output
) = parse_len_output(cx
, cx
.tcx
.fn_sig(item
.owner_id
).subst_identity().skip_binder());
150 let (name
, kind
) = match cx
.tcx
.hir().find(ty_hir_id
) {
151 Some(Node
::ForeignItem(x
)) => (x
.ident
.name
, "extern type"),
152 Some(Node
::Item(x
)) => match x
.kind
{
153 ItemKind
::Struct(..) => (x
.ident
.name
, "struct"),
154 ItemKind
::Enum(..) => (x
.ident
.name
, "enum"),
155 ItemKind
::Union(..) => (x
.ident
.name
, "union"),
156 _
=> (x
.ident
.name
, "type"),
160 check_for_is_empty(cx
, sig
.span
, sig
.decl
.implicit_self
, output
, ty_id
, name
, kind
)
165 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
166 if expr
.span
.from_expansion() {
170 if let ExprKind
::Binary(Spanned { node: cmp, .. }
, left
, right
) = expr
.kind
{
171 // expr.span might contains parenthesis, see issue #10529
172 let actual_span
= left
.span
.with_hi(right
.span
.hi());
175 check_cmp(cx
, actual_span
, left
, right
, "", 0); // len == 0
176 check_cmp(cx
, actual_span
, right
, left
, "", 0); // 0 == len
179 check_cmp(cx
, actual_span
, left
, right
, "!", 0); // len != 0
180 check_cmp(cx
, actual_span
, right
, left
, "!", 0); // 0 != len
183 check_cmp(cx
, actual_span
, left
, right
, "!", 0); // len > 0
184 check_cmp(cx
, actual_span
, right
, left
, "", 1); // 1 > len
187 check_cmp(cx
, actual_span
, left
, right
, "", 1); // len < 1
188 check_cmp(cx
, actual_span
, right
, left
, "!", 0); // 0 < len
190 BinOpKind
::Ge
=> check_cmp(cx
, actual_span
, left
, right
, "!", 1), // len >= 1
191 BinOpKind
::Le
=> check_cmp(cx
, actual_span
, right
, left
, "!", 1), // 1 <= len
198 fn check_trait_items(cx
: &LateContext
<'_
>, visited_trait
: &Item
<'_
>, trait_items
: &[TraitItemRef
]) {
199 fn is_named_self(cx
: &LateContext
<'_
>, item
: &TraitItemRef
, name
: Symbol
) -> bool
{
200 item
.ident
.name
== name
201 && if let AssocItemKind
::Fn { has_self }
= item
.kind
{
204 .fn_sig(item
.id
.owner_id
)
216 // fill the set with current and super traits
217 fn fill_trait_set(traitt
: DefId
, set
: &mut DefIdSet
, cx
: &LateContext
<'_
>) {
218 if set
.insert(traitt
) {
219 for supertrait
in rustc_trait_selection
::traits
::supertrait_def_ids(cx
.tcx
, traitt
) {
220 fill_trait_set(supertrait
, set
, cx
);
225 if cx
.effective_visibilities
.is_exported(visited_trait
.owner_id
.def_id
)
226 && trait_items
.iter().any(|i
| is_named_self(cx
, i
, sym
::len
))
228 let mut current_and_super_traits
= DefIdSet
::default();
229 fill_trait_set(visited_trait
.owner_id
.to_def_id(), &mut current_and_super_traits
, cx
);
230 let is_empty
= sym
!(is_empty
);
232 let is_empty_method_found
= current_and_super_traits
234 .flat_map(|&i
| cx
.tcx
.associated_items(i
).filter_by_name_unhygienic(is_empty
))
236 i
.kind
== ty
::AssocKind
::Fn
237 && i
.fn_has_self_parameter
238 && cx
.tcx
.fn_sig(i
.def_id
).skip_binder().inputs().skip_binder().len() == 1
241 if !is_empty_method_found
{
244 LEN_WITHOUT_IS_EMPTY
,
247 "trait `{}` has a `len` method but no (possibly inherited) `is_empty` method",
248 visited_trait
.ident
.name
255 #[derive(Debug, Clone, Copy)]
262 fn extract_future_output
<'tcx
>(cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>) -> Option
<&'tcx PathSegment
<'tcx
>> {
263 if let ty
::Alias(_
, alias_ty
) = ty
.kind() &&
264 let Some(Node
::Item(item
)) = cx
.tcx
.hir().get_if_local(alias_ty
.def_id
) &&
265 let Item { kind: ItemKind::OpaqueTy(opaque), .. }
= item
&&
266 opaque
.bounds
.len() == 1 &&
267 let GenericBound
::LangItemTrait(LangItem
::Future
, _
, _
, generic_args
) = &opaque
.bounds
[0] &&
268 generic_args
.bindings
.len() == 1 &&
269 let TypeBindingKind
::Equality
{
270 term
: rustc_hir
::Term
::Ty(rustc_hir
::Ty {kind: TyKind::Path(QPath::Resolved(_, path)), .. }
),
271 } = &generic_args
.bindings
[0].kind
&&
272 path
.segments
.len() == 1 {
273 return Some(&path
.segments
[0]);
279 fn is_first_generic_integral
<'tcx
>(segment
: &'tcx PathSegment
<'tcx
>) -> bool
{
280 if let Some(generic_args
) = segment
.args
{
281 if generic_args
.args
.is_empty() {
284 let arg
= &generic_args
.args
[0];
285 if let GenericArg
::Type(rustc_hir
::Ty
{
286 kind
: TyKind
::Path(QPath
::Resolved(_
, path
)),
290 let segments
= &path
.segments
;
291 let segment
= &segments
[0];
292 let res
= &segment
.res
;
293 if matches
!(res
, Res
::PrimTy(PrimTy
::Uint(_
))) || matches
!(res
, Res
::PrimTy(PrimTy
::Int(_
))) {
302 fn parse_len_output
<'tcx
>(cx
: &LateContext
<'tcx
>, sig
: FnSig
<'tcx
>) -> Option
<LenOutput
> {
303 if let Some(segment
) = extract_future_output(cx
, sig
.output()) {
304 let res
= segment
.res
;
306 if matches
!(res
, Res
::PrimTy(PrimTy
::Uint(_
))) || matches
!(res
, Res
::PrimTy(PrimTy
::Int(_
))) {
307 return Some(LenOutput
::Integral
);
310 if let Res
::Def(_
, def_id
) = res
{
311 if cx
.tcx
.is_diagnostic_item(sym
::Option
, def_id
) && is_first_generic_integral(segment
) {
312 return Some(LenOutput
::Option(def_id
));
313 } else if cx
.tcx
.is_diagnostic_item(sym
::Result
, def_id
) && is_first_generic_integral(segment
) {
314 return Some(LenOutput
::Result(def_id
));
321 match *sig
.output().kind() {
322 ty
::Int(_
) | ty
::Uint(_
) => Some(LenOutput
::Integral
),
323 ty
::Adt(adt
, subs
) if cx
.tcx
.is_diagnostic_item(sym
::Option
, adt
.did()) => {
324 subs
.type_at(0).is_integral().then(|| LenOutput
::Option(adt
.did()))
326 ty
::Adt(adt
, subs
) if cx
.tcx
.is_diagnostic_item(sym
::Result
, adt
.did()) => {
327 subs
.type_at(0).is_integral().then(|| LenOutput
::Result(adt
.did()))
334 fn matches_is_empty_output
<'tcx
>(self, cx
: &LateContext
<'tcx
>, ty
: Ty
<'tcx
>) -> bool
{
335 if let Some(segment
) = extract_future_output(cx
, ty
) {
336 return match (self, segment
.res
) {
337 (_
, Res
::PrimTy(PrimTy
::Bool
)) => true,
338 (Self::Option(_
), Res
::Def(_
, def_id
)) if cx
.tcx
.is_diagnostic_item(sym
::Option
, def_id
) => true,
339 (Self::Result(_
), Res
::Def(_
, def_id
)) if cx
.tcx
.is_diagnostic_item(sym
::Result
, def_id
) => true,
344 match (self, ty
.kind()) {
345 (_
, &ty
::Bool
) => true,
346 (Self::Option(id
), &ty
::Adt(adt
, subs
)) if id
== adt
.did() => subs
.type_at(0).is_bool(),
347 (Self::Result(id
), &ty
::Adt(adt
, subs
)) if id
== adt
.did() => subs
.type_at(0).is_bool(),
352 fn expected_sig(self, self_kind
: ImplicitSelfKind
) -> String
{
353 let self_ref
= match self_kind
{
354 ImplicitSelfKind
::ImmRef
=> "&",
355 ImplicitSelfKind
::MutRef
=> "&mut ",
359 Self::Integral
=> format
!("expected signature: `({self_ref}self) -> bool`"),
361 format
!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Option<bool>")
363 Self::Result(..) => {
364 format
!("expected signature: `({self_ref}self) -> bool` or `({self_ref}self) -> Result<bool>")
370 /// Checks if the given signature matches the expectations for `is_empty`
371 fn check_is_empty_sig
<'tcx
>(
372 cx
: &LateContext
<'tcx
>,
374 self_kind
: ImplicitSelfKind
,
375 len_output
: LenOutput
,
377 match &**sig
.inputs_and_output
{
378 [arg
, res
] if len_output
.matches_is_empty_output(cx
, *res
) => {
380 (arg
.kind(), self_kind
),
381 (ty
::Ref(_
, _
, Mutability
::Not
), ImplicitSelfKind
::ImmRef
)
382 | (ty
::Ref(_
, _
, Mutability
::Mut
), ImplicitSelfKind
::MutRef
)
383 ) || (!arg
.is_ref() && matches
!(self_kind
, ImplicitSelfKind
::Imm
| ImplicitSelfKind
::Mut
))
389 /// Checks if the given type has an `is_empty` method with the appropriate signature.
390 fn check_for_is_empty(
391 cx
: &LateContext
<'_
>,
393 self_kind
: ImplicitSelfKind
,
399 let is_empty
= Symbol
::intern("is_empty");
402 .inherent_impls(impl_ty
)
404 .flat_map(|&id
| cx
.tcx
.associated_items(id
).filter_by_name_unhygienic(is_empty
))
405 .find(|item
| item
.kind
== AssocKind
::Fn
);
407 let (msg
, is_empty_span
, self_kind
) = match is_empty
{
410 "{item_kind} `{}` has a public `len` method, but no `is_empty` method",
416 Some(is_empty
) if !cx
.effective_visibilities
.is_exported(is_empty
.def_id
.expect_local()) => (
418 "{item_kind} `{}` has a public `len` method, but a private `is_empty` method",
421 Some(cx
.tcx
.def_span(is_empty
.def_id
)),
425 if !(is_empty
.fn_has_self_parameter
426 && check_is_empty_sig(
428 cx
.tcx
.fn_sig(is_empty
.def_id
).subst_identity().skip_binder(),
435 "{item_kind} `{}` has a public `len` method, but the `is_empty` method has an unexpected signature",
438 Some(cx
.tcx
.def_span(is_empty
.def_id
)),
445 span_lint_and_then(cx
, LEN_WITHOUT_IS_EMPTY
, span
, &msg
, |db
| {
446 if let Some(span
) = is_empty_span
{
447 db
.span_note(span
, "`is_empty` defined here");
449 if let Some(self_kind
) = self_kind
{
450 db
.note(output
.expected_sig(self_kind
));
455 fn check_cmp(cx
: &LateContext
<'_
>, span
: Span
, method
: &Expr
<'_
>, lit
: &Expr
<'_
>, op
: &str, compare_to
: u32) {
456 if let (&ExprKind
::MethodCall(method_path
, receiver
, args
, _
), ExprKind
::Lit(lit
)) = (&method
.kind
, &lit
.kind
) {
457 // check if we are in an is_empty() method
458 if let Some(name
) = get_item_name(cx
, method
) {
459 if name
.as_str() == "is_empty" {
467 method_path
.ident
.name
,
475 check_empty_expr(cx
, span
, method
, lit
, op
);
479 // FIXME(flip1995): Figure out how to reduce the number of arguments
480 #[allow(clippy::too_many_arguments)]
482 cx
: &LateContext
<'_
>,
491 if let LitKind
::Int(lit
, _
) = *lit
{
492 // check if length is compared to the specified number
493 if lit
!= u128
::from(compare_to
) {
497 if method_name
== sym
::len
&& args
.is_empty() && has_is_empty(cx
, receiver
) {
498 let mut applicability
= Applicability
::MachineApplicable
;
503 &format
!("length comparison to {}", if compare_to
== 0 { "zero" }
else { "one" }
),
504 &format
!("using `{op}is_empty` is clearer and more explicit"),
507 snippet_with_context(cx
, receiver
.span
, span
.ctxt(), "_", &mut applicability
).0,
515 fn check_empty_expr(cx
: &LateContext
<'_
>, span
: Span
, lit1
: &Expr
<'_
>, lit2
: &Expr
<'_
>, op
: &str) {
516 if (is_empty_array(lit2
) || is_empty_string(lit2
)) && has_is_empty(cx
, lit1
) {
517 let mut applicability
= Applicability
::MachineApplicable
;
519 let lit1
= peel_ref_operators(cx
, lit1
);
520 let lit_str
= Sugg
::hir_with_context(cx
, lit1
, span
.ctxt(), "_", &mut applicability
).maybe_par();
526 "comparison to empty slice",
527 &format
!("using `{op}is_empty` is clearer and more explicit"),
528 format
!("{op}{lit_str}.is_empty()"),
534 fn is_empty_string(expr
: &Expr
<'_
>) -> bool
{
535 if let ExprKind
::Lit(lit
) = expr
.kind
{
536 if let LitKind
::Str(lit
, _
) = lit
.node
{
537 let lit
= lit
.as_str();
538 return lit
.is_empty();
544 fn is_empty_array(expr
: &Expr
<'_
>) -> bool
{
545 if let ExprKind
::Array(arr
) = expr
.kind
{
546 return arr
.is_empty();
551 /// Checks if this type has an `is_empty` method.
552 fn has_is_empty(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
553 /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
554 fn is_is_empty(cx
: &LateContext
<'_
>, item
: &ty
::AssocItem
) -> bool
{
555 if item
.kind
== ty
::AssocKind
::Fn
{
556 let sig
= cx
.tcx
.fn_sig(item
.def_id
).skip_binder();
557 let ty
= sig
.skip_binder();
558 ty
.inputs().len() == 1
564 /// Checks the inherent impl's items for an `is_empty(self)` method.
565 fn has_is_empty_impl(cx
: &LateContext
<'_
>, id
: DefId
) -> bool
{
566 let is_empty
= sym
!(is_empty
);
567 cx
.tcx
.inherent_impls(id
).iter().any(|imp
| {
569 .associated_items(*imp
)
570 .filter_by_name_unhygienic(is_empty
)
571 .any(|item
| is_is_empty(cx
, item
))
575 let ty
= &cx
.typeck_results().expr_ty(expr
).peel_refs();
577 ty
::Dynamic(tt
, ..) => tt
.principal().map_or(false, |principal
| {
578 let is_empty
= sym
!(is_empty
);
580 .associated_items(principal
.def_id())
581 .filter_by_name_unhygienic(is_empty
)
582 .any(|item
| is_is_empty(cx
, item
))
584 ty
::Alias(ty
::Projection
, ref proj
) => has_is_empty_impl(cx
, proj
.def_id
),
585 ty
::Adt(id
, _
) => has_is_empty_impl(cx
, id
.did()),
586 ty
::Array(..) | ty
::Slice(..) | ty
::Str
=> true,