diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 4c133c06a15..3babc2a5895 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed}; +use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def_id::DefIdSet; use rustc_hir::{ def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item, - ItemKind, Mutability, Node, TraitItemRef, TyKind, + ItemKind, Mutability, Node, TraitItemRef, TyKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, AssocKind, FnSig, Ty}; @@ -16,6 +16,7 @@ use rustc_span::{ source_map::{Span, Spanned, Symbol}, symbol::sym, }; +use std::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -428,16 +429,23 @@ fn check_len( fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Expr<'_>, op: &str) { if (is_empty_array(lit2) || is_empty_string(lit2)) && has_is_empty(cx, lit1) { let mut applicability = Applicability::MachineApplicable; + + let lit1 = peel_ref_operators(cx, lit1); + let mut lit_str = snippet_with_applicability(cx, lit1.span, "_", &mut applicability); + + // Wrap the expression in parentheses if it's a deref expression. Otherwise operator precedence will + // cause the code to dereference boolean(won't compile). + if let ExprKind::Unary(UnOp::Deref, _) = lit1.kind { + lit_str = Cow::from(format!("({lit_str})")); + } + span_lint_and_sugg( cx, COMPARISON_TO_EMPTY, span, "comparison to empty slice", &format!("using `{op}is_empty` is clearer and more explicit"), - format!( - "{op}{}.is_empty()", - snippet_with_applicability(cx, lit1.span, "_", &mut applicability) - ), + format!("{op}{lit_str}.is_empty()"), applicability, ); } diff --git a/tests/ui/len_zero.fixed b/tests/ui/len_zero.fixed index 1f3b8ac99b1..c1c0b5ae40f 100644 --- a/tests/ui/len_zero.fixed +++ b/tests/ui/len_zero.fixed @@ -3,6 +3,9 @@ #![warn(clippy::len_zero)] #![allow(dead_code, unused, clippy::len_without_is_empty)] +extern crate core; +use core::ops::Deref; + pub struct One; struct Wither; @@ -56,6 +59,26 @@ impl WithIsEmpty for Wither { } } +struct DerefToDerefToString; + +impl Deref for DerefToDerefToString { + type Target = DerefToString; + + fn deref(&self) -> &Self::Target { + &DerefToString {} + } +} + +struct DerefToString; + +impl Deref for DerefToString { + type Target = str; + + fn deref(&self) -> &Self::Target { + "Hello, world!" + } +} + fn main() { let x = [1, 2]; if x.is_empty() { @@ -64,6 +87,23 @@ fn main() { if "".is_empty() {} + let s = "Hello, world!"; + let s1 = &s; + let s2 = &s1; + let s3 = &s2; + let s4 = &s3; + let s5 = &s4; + let s6 = &s5; + println!("{}", s1.is_empty()); + println!("{}", s2.is_empty()); + println!("{}", s3.is_empty()); + println!("{}", s4.is_empty()); + println!("{}", s5.is_empty()); + println!("{}", (s6).is_empty()); + + let d2s = DerefToDerefToString {}; + println!("{}", (**d2s).is_empty()); + let y = One; if y.len() == 0 { // No error; `One` does not have `.is_empty()`. diff --git a/tests/ui/len_zero.rs b/tests/ui/len_zero.rs index dc21de0001b..cc2eb05b6bf 100644 --- a/tests/ui/len_zero.rs +++ b/tests/ui/len_zero.rs @@ -3,6 +3,9 @@ #![warn(clippy::len_zero)] #![allow(dead_code, unused, clippy::len_without_is_empty)] +extern crate core; +use core::ops::Deref; + pub struct One; struct Wither; @@ -56,6 +59,26 @@ impl WithIsEmpty for Wither { } } +struct DerefToDerefToString; + +impl Deref for DerefToDerefToString { + type Target = DerefToString; + + fn deref(&self) -> &Self::Target { + &DerefToString {} + } +} + +struct DerefToString; + +impl Deref for DerefToString { + type Target = str; + + fn deref(&self) -> &Self::Target { + "Hello, world!" + } +} + fn main() { let x = [1, 2]; if x.len() == 0 { @@ -64,6 +87,23 @@ fn main() { if "".len() == 0 {} + let s = "Hello, world!"; + let s1 = &s; + let s2 = &s1; + let s3 = &s2; + let s4 = &s3; + let s5 = &s4; + let s6 = &s5; + println!("{}", *s1 == ""); + println!("{}", **s2 == ""); + println!("{}", ***s3 == ""); + println!("{}", ****s4 == ""); + println!("{}", *****s5 == ""); + println!("{}", ******(s6) == ""); + + let d2s = DerefToDerefToString {}; + println!("{}", &**d2s == ""); + let y = One; if y.len() == 0 { // No error; `One` does not have `.is_empty()`. diff --git a/tests/ui/len_zero.stderr b/tests/ui/len_zero.stderr index 6c71f1beeac..b6f13780253 100644 --- a/tests/ui/len_zero.stderr +++ b/tests/ui/len_zero.stderr @@ -1,5 +1,5 @@ error: length comparison to zero - --> $DIR/len_zero.rs:61:8 + --> $DIR/len_zero.rs:84:8 | LL | if x.len() == 0 { | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `x.is_empty()` @@ -7,82 +7,126 @@ LL | if x.len() == 0 { = note: `-D clippy::len-zero` implied by `-D warnings` error: length comparison to zero - --> $DIR/len_zero.rs:65:8 + --> $DIR/len_zero.rs:88:8 | LL | if "".len() == 0 {} | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `"".is_empty()` +error: comparison to empty slice + --> $DIR/len_zero.rs:97:20 + | +LL | println!("{}", *s1 == ""); + | ^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s1.is_empty()` + | + = note: `-D clippy::comparison-to-empty` implied by `-D warnings` + +error: comparison to empty slice + --> $DIR/len_zero.rs:98:20 + | +LL | println!("{}", **s2 == ""); + | ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s2.is_empty()` + +error: comparison to empty slice + --> $DIR/len_zero.rs:99:20 + | +LL | println!("{}", ***s3 == ""); + | ^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s3.is_empty()` + +error: comparison to empty slice + --> $DIR/len_zero.rs:100:20 + | +LL | println!("{}", ****s4 == ""); + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s4.is_empty()` + +error: comparison to empty slice + --> $DIR/len_zero.rs:101:20 + | +LL | println!("{}", *****s5 == ""); + | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s5.is_empty()` + +error: comparison to empty slice + --> $DIR/len_zero.rs:102:20 + | +LL | println!("{}", ******(s6) == ""); + | ^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(s6).is_empty()` + +error: comparison to empty slice + --> $DIR/len_zero.rs:105:20 + | +LL | println!("{}", &**d2s == ""); + | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(**d2s).is_empty()` + error: length comparison to zero - --> $DIR/len_zero.rs:80:8 + --> $DIR/len_zero.rs:120:8 | LL | if has_is_empty.len() == 0 { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:83:8 + --> $DIR/len_zero.rs:123:8 | LL | if has_is_empty.len() != 0 { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:86:8 + --> $DIR/len_zero.rs:126:8 | LL | if has_is_empty.len() > 0 { | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to one - --> $DIR/len_zero.rs:89:8 + --> $DIR/len_zero.rs:129:8 | LL | if has_is_empty.len() < 1 { | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` error: length comparison to one - --> $DIR/len_zero.rs:92:8 + --> $DIR/len_zero.rs:132:8 | LL | if has_is_empty.len() >= 1 { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:103:8 + --> $DIR/len_zero.rs:143:8 | LL | if 0 == has_is_empty.len() { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:106:8 + --> $DIR/len_zero.rs:146:8 | LL | if 0 != has_is_empty.len() { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:109:8 + --> $DIR/len_zero.rs:149:8 | LL | if 0 < has_is_empty.len() { | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to one - --> $DIR/len_zero.rs:112:8 + --> $DIR/len_zero.rs:152:8 | LL | if 1 <= has_is_empty.len() { | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` error: length comparison to one - --> $DIR/len_zero.rs:115:8 + --> $DIR/len_zero.rs:155:8 | LL | if 1 > has_is_empty.len() { | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:129:8 + --> $DIR/len_zero.rs:169:8 | LL | if with_is_empty.len() == 0 { | ^^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `with_is_empty.is_empty()` error: length comparison to zero - --> $DIR/len_zero.rs:142:8 + --> $DIR/len_zero.rs:182:8 | LL | if b.len() != 0 {} | ^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!b.is_empty()` -error: aborting due to 14 previous errors +error: aborting due to 21 previous errors