Auto merge of #12310 - samueltardieu:issue-12307, r=xFrednet

New lint `const_is_empty`

This lint detects calls to `.is_empty()` on an entity initialized from a string literal and flag them as suspicious. To avoid triggering on macros called from generated code, it checks that the `.is_empty()` receiver, the call itself and the initialization come from the same context.

Fixes #12307

changelog: [`const_is_empty`]: new lint
This commit is contained in:
bors 2024-03-09 09:56:37 +00:00
commit 453242cbde
18 changed files with 566 additions and 38 deletions

View File

@ -5110,6 +5110,7 @@ Released 2018-09-13
[`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read [`collection_is_never_read`]: https://rust-lang.github.io/rust-clippy/master/index.html#collection_is_never_read
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain [`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty [`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`const_is_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_is_empty
[`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime [`const_static_lifetime`]: https://rust-lang.github.io/rust-clippy/master/index.html#const_static_lifetime
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator [`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
[`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def [`crate_in_macro_def`]: https://rust-lang.github.io/rust-clippy/master/index.html#crate_in_macro_def

View File

@ -353,6 +353,7 @@
crate::methods::CLONE_ON_COPY_INFO, crate::methods::CLONE_ON_COPY_INFO,
crate::methods::CLONE_ON_REF_PTR_INFO, crate::methods::CLONE_ON_REF_PTR_INFO,
crate::methods::COLLAPSIBLE_STR_REPLACE_INFO, crate::methods::COLLAPSIBLE_STR_REPLACE_INFO,
crate::methods::CONST_IS_EMPTY_INFO,
crate::methods::DRAIN_COLLECT_INFO, crate::methods::DRAIN_COLLECT_INFO,
crate::methods::ERR_EXPECT_INFO, crate::methods::ERR_EXPECT_INFO,
crate::methods::EXPECT_FUN_CALL_INFO, crate::methods::EXPECT_FUN_CALL_INFO,

View File

@ -0,0 +1,49 @@
use clippy_utils::consts::constant_is_empty;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{find_binding_init, path_to_local};
use rustc_hir::{Expr, HirId};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_span::sym;
use super::CONST_IS_EMPTY;
/// Expression whose initialization depend on a constant conditioned by a `#[cfg(…)]` directive will
/// not trigger the lint.
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) {
return;
}
let init_expr = expr_or_init(cx, receiver);
if !receiver.span.eq_ctxt(init_expr.span) {
return;
}
if let Some(init_is_empty) = constant_is_empty(cx, init_expr) {
span_lint(
cx,
CONST_IS_EMPTY,
expr.span,
&format!("this expression always evaluates to {init_is_empty:?}"),
);
}
}
fn is_under_cfg(cx: &LateContext<'_>, id: HirId) -> bool {
cx.tcx
.hir()
.parent_id_iter(id)
.any(|id| cx.tcx.hir().attrs(id).iter().any(|attr| attr.has_name(sym::cfg)))
}
/// Similar to [`clippy_utils::expr_or_init`], but does not go up the chain if the initialization
/// value depends on a `#[cfg(…)]` directive.
fn expr_or_init<'a, 'b, 'tcx: 'b>(cx: &LateContext<'tcx>, mut expr: &'a Expr<'b>) -> &'a Expr<'b> {
while let Some(init) = path_to_local(expr)
.and_then(|id| find_binding_init(cx, id))
.filter(|init| cx.typeck_results().expr_adjustments(init).is_empty())
.filter(|init| !is_under_cfg(cx, init.hir_id))
{
expr = init;
}
expr
}

View File

@ -36,6 +36,7 @@
mod inspect_for_each; mod inspect_for_each;
mod into_iter_on_ref; mod into_iter_on_ref;
mod is_digit_ascii_radix; mod is_digit_ascii_radix;
mod is_empty;
mod iter_cloned_collect; mod iter_cloned_collect;
mod iter_count; mod iter_count;
mod iter_filter; mod iter_filter;
@ -4044,6 +4045,31 @@
"calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`" "calling `.get().is_some()` or `.get().is_none()` instead of `.contains()` or `.contains_key()`"
} }
declare_clippy_lint! {
/// ### What it does
/// It identifies calls to `.is_empty()` on constant values.
///
/// ### Why is this bad?
/// String literals and constant values are known at compile time. Checking if they
/// are empty will always return the same value. This might not be the intention of
/// the expression.
///
/// ### Example
/// ```no_run
/// let value = "";
/// if value.is_empty() {
/// println!("the string is empty");
/// }
/// ```
/// Use instead:
/// ```no_run
/// println!("the string is empty");
/// ```
#[clippy::version = "1.78.0"]
pub CONST_IS_EMPTY,
suspicious,
"is_empty() called on strings known at compile time"
}
pub struct Methods { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Msrv, msrv: Msrv,
@ -4092,6 +4118,7 @@ pub fn new(
CLONE_ON_COPY, CLONE_ON_COPY,
CLONE_ON_REF_PTR, CLONE_ON_REF_PTR,
COLLAPSIBLE_STR_REPLACE, COLLAPSIBLE_STR_REPLACE,
CONST_IS_EMPTY,
ITER_OVEREAGER_CLONED, ITER_OVEREAGER_CLONED,
CLONED_INSTEAD_OF_COPIED, CLONED_INSTEAD_OF_COPIED,
FLAT_MAP_OPTION, FLAT_MAP_OPTION,
@ -4445,7 +4472,7 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
("as_deref" | "as_deref_mut", []) => { ("as_deref" | "as_deref_mut", []) => {
needless_option_as_deref::check(cx, expr, recv, name); needless_option_as_deref::check(cx, expr, recv, name);
}, },
("as_bytes" | "is_empty", []) => { ("as_bytes", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span); redundant_as_str::check(cx, expr, recv, as_str_span, span);
} }
@ -4619,6 +4646,12 @@ fn check_methods<'tcx>(&self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
("hash", [arg]) => { ("hash", [arg]) => {
unit_hash::check(cx, expr, recv, arg); unit_hash::check(cx, expr, recv, arg);
}, },
("is_empty", []) => {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
is_empty::check(cx, expr, recv);
},
("is_file", []) => filetype_is_file::check(cx, expr, recv), ("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv), ("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false), ("is_none", []) => check_is_some_is_none(cx, expr, recv, call_span, false),

View File

@ -10,8 +10,10 @@
use rustc_lexer::tokenize; use rustc_lexer::tokenize;
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::mir::interpret::{alloc_range, Scalar}; use rustc_middle::mir::interpret::{alloc_range, Scalar};
use rustc_middle::mir::ConstValue;
use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, IntTy, List, ScalarInt, Ty, TyCtxt, UintTy}; use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, IntTy, List, ScalarInt, Ty, TyCtxt, UintTy};
use rustc_middle::{bug, mir, span_bug}; use rustc_middle::{bug, mir, span_bug};
use rustc_span::def_id::DefId;
use rustc_span::symbol::{Ident, Symbol}; use rustc_span::symbol::{Ident, Symbol};
use rustc_span::SyntaxContext; use rustc_span::SyntaxContext;
use rustc_target::abi::Size; use rustc_target::abi::Size;
@ -307,6 +309,12 @@ pub fn is_local(&self) -> bool {
} }
} }
/// Attempts to check whether the expression is a constant representing an empty slice, str, array,
/// etc…
pub fn constant_is_empty(lcx: &LateContext<'_>, e: &Expr<'_>) -> Option<bool> {
ConstEvalLateContext::new(lcx, lcx.typeck_results()).expr_is_empty(e)
}
/// Attempts to evaluate the expression as a constant. /// Attempts to evaluate the expression as a constant.
pub fn constant<'tcx>( pub fn constant<'tcx>(
lcx: &LateContext<'tcx>, lcx: &LateContext<'tcx>,
@ -406,7 +414,13 @@ pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
match e.kind { match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr(e), ExprKind::DropTemps(e) => self.expr(e),
ExprKind::Path(ref qpath) => self.fetch_path(qpath, e.hir_id, self.typeck_results.expr_ty(e)), ExprKind::Path(ref qpath) => {
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| {
let result = mir_to_const(this.lcx, result)?;
this.source = ConstantSource::Constant;
Some(result)
})
},
ExprKind::Block(block, _) => self.block(block), ExprKind::Block(block, _) => self.block(block),
ExprKind::Lit(lit) => { ExprKind::Lit(lit) => {
if is_direct_expn_of(e.span, "cfg").is_some() { if is_direct_expn_of(e.span, "cfg").is_some() {
@ -472,6 +486,49 @@ pub fn expr(&mut self, e: &Expr<'_>) -> Option<Constant<'tcx>> {
} }
} }
/// Simple constant folding to determine if an expression is an empty slice, str, array, …
/// `None` will be returned if the constness cannot be determined, or if the resolution
/// leaves the local crate.
pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option<bool> {
match e.kind {
ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr_is_empty(self.lcx.tcx.hir().body(body).value),
ExprKind::DropTemps(e) => self.expr_is_empty(e),
ExprKind::Path(ref qpath) => {
if !self
.typeck_results
.qpath_res(qpath, e.hir_id)
.opt_def_id()
.is_some_and(DefId::is_local)
{
return None;
}
self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| {
mir_is_empty(this.lcx, result)
})
},
ExprKind::Lit(lit) => {
if is_direct_expn_of(e.span, "cfg").is_some() {
None
} else {
match &lit.node {
LitKind::Str(is, _) => Some(is.is_empty()),
LitKind::ByteStr(s, _) | LitKind::CStr(s, _) => Some(s.is_empty()),
_ => None,
}
}
},
ExprKind::Array(vec) => self.multi(vec).map(|v| v.is_empty()),
ExprKind::Repeat(..) => {
if let ty::Array(_, n) = self.typeck_results.expr_ty(e).kind() {
Some(n.try_eval_target_usize(self.lcx.tcx, self.lcx.param_env)? == 0)
} else {
span_bug!(e.span, "typeck error");
}
},
_ => None,
}
}
#[expect(clippy::cast_possible_wrap)] #[expect(clippy::cast_possible_wrap)]
fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>> { fn constant_not(&self, o: &Constant<'tcx>, ty: Ty<'_>) -> Option<Constant<'tcx>> {
use self::Constant::{Bool, Int}; use self::Constant::{Bool, Int};
@ -519,8 +576,11 @@ fn multi(&mut self, vec: &[Expr<'_>]) -> Option<Vec<Constant<'tcx>>> {
vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>() vec.iter().map(|elem| self.expr(elem)).collect::<Option<_>>()
} }
/// Lookup a possibly constant expression from an `ExprKind::Path`. /// Lookup a possibly constant expression from an `ExprKind::Path` and apply a function on it.
fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<Constant<'tcx>> { fn fetch_path_and_apply<T, F>(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>, f: F) -> Option<T>
where
F: FnOnce(&mut Self, rustc_middle::mir::Const<'tcx>) -> Option<T>,
{
let res = self.typeck_results.qpath_res(qpath, id); let res = self.typeck_results.qpath_res(qpath, id);
match res { match res {
Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => { Res::Def(DefKind::Const | DefKind::AssocConst, def_id) => {
@ -553,9 +613,7 @@ fn fetch_path(&mut self, qpath: &QPath<'_>, id: HirId, ty: Ty<'tcx>) -> Option<C
.const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, args), None) .const_eval_resolve(self.param_env, mir::UnevaluatedConst::new(def_id, args), None)
.ok() .ok()
.map(|val| rustc_middle::mir::Const::from_value(val, ty))?; .map(|val| rustc_middle::mir::Const::from_value(val, ty))?;
let result = mir_to_const(self.lcx, result)?; f(self, result)
self.source = ConstantSource::Constant;
Some(result)
}, },
_ => None, _ => None,
} }
@ -746,7 +804,6 @@ fn binop(&mut self, op: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> Option<Cons
} }
pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> Option<Constant<'tcx>> { pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> Option<Constant<'tcx>> {
use rustc_middle::mir::ConstValue;
let mir::Const::Val(val, _) = result else { let mir::Const::Val(val, _) = result else {
// We only work on evaluated consts. // We only work on evaluated consts.
return None; return None;
@ -794,6 +851,42 @@ pub fn mir_to_const<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) ->
} }
} }
fn mir_is_empty<'tcx>(lcx: &LateContext<'tcx>, result: mir::Const<'tcx>) -> Option<bool> {
let mir::Const::Val(val, _) = result else {
// We only work on evaluated consts.
return None;
};
match (val, result.ty().kind()) {
(_, ty::Ref(_, inner_ty, _)) => match inner_ty.kind() {
ty::Str | ty::Slice(_) => {
if let ConstValue::Indirect { alloc_id, offset } = val {
// Get the length from the slice, using the same formula as
// [`ConstValue::try_get_slice_bytes_for_diagnostics`].
let a = lcx.tcx.global_alloc(alloc_id).unwrap_memory().inner();
let ptr_size = lcx.tcx.data_layout.pointer_size;
if a.size() < offset + 2 * ptr_size {
// (partially) dangling reference
return None;
}
let len = a
.read_scalar(&lcx.tcx, alloc_range(offset + ptr_size, ptr_size), false)
.ok()?
.to_target_usize(&lcx.tcx)
.ok()?;
Some(len == 0)
} else {
None
}
},
ty::Array(_, len) => Some(len.try_to_target_usize(lcx.tcx)? == 0),
_ => None,
},
(ConstValue::Indirect { .. }, ty::Array(_, len)) => Some(len.try_to_target_usize(lcx.tcx)? == 0),
(ConstValue::ZeroSized, _) => Some(true),
_ => None,
}
}
fn field_of_struct<'tcx>( fn field_of_struct<'tcx>(
adt_def: ty::AdtDef<'tcx>, adt_def: ty::AdtDef<'tcx>,
lcx: &LateContext<'tcx>, lcx: &LateContext<'tcx>,

View File

@ -1,4 +1,4 @@
#![allow(unused, clippy::assertions_on_constants)] #![allow(unused, clippy::assertions_on_constants, clippy::const_is_empty)]
#![warn(clippy::bool_assert_comparison)] #![warn(clippy::bool_assert_comparison)]
use std::ops::Not; use std::ops::Not;

View File

@ -1,4 +1,4 @@
#![allow(unused, clippy::assertions_on_constants)] #![allow(unused, clippy::assertions_on_constants, clippy::const_is_empty)]
#![warn(clippy::bool_assert_comparison)] #![warn(clippy::bool_assert_comparison)]
use std::ops::Not; use std::ops::Not;

174
tests/ui/const_is_empty.rs Normal file
View File

@ -0,0 +1,174 @@
#![feature(inline_const)]
#![warn(clippy::const_is_empty)]
#![allow(clippy::needless_late_init, unused_must_use)]
fn test_literal() {
if "".is_empty() {
//~^ERROR: this expression always evaluates to true
}
if "foobar".is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
fn test_byte_literal() {
if b"".is_empty() {
//~^ERROR: this expression always evaluates to true
}
if b"foobar".is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
fn test_no_mut() {
let mut empty = "";
if empty.is_empty() {
// No lint because it is mutable
}
}
fn test_propagated() {
let empty = "";
let non_empty = "foobar";
let empty2 = empty;
let non_empty2 = non_empty;
if empty2.is_empty() {
//~^ERROR: this expression always evaluates to true
}
if non_empty2.is_empty() {
//~^ERROR: this expression always evaluates to false
}
}
const EMPTY_STR: &str = "";
const NON_EMPTY_STR: &str = "foo";
const EMPTY_BSTR: &[u8] = b"";
const NON_EMPTY_BSTR: &[u8] = b"foo";
const EMPTY_U8_SLICE: &[u8] = &[];
const NON_EMPTY_U8_SLICE: &[u8] = &[1, 2];
const EMPTY_SLICE: &[u32] = &[];
const NON_EMPTY_SLICE: &[u32] = &[1, 2];
const NON_EMPTY_SLICE_REPEAT: &[u32] = &[1; 2];
const EMPTY_ARRAY: [u32; 0] = [];
const EMPTY_ARRAY_REPEAT: [u32; 0] = [1; 0];
const NON_EMPTY_ARRAY: [u32; 2] = [1, 2];
const NON_EMPTY_ARRAY_REPEAT: [u32; 2] = [1; 2];
const EMPTY_REF_ARRAY: &[u32; 0] = &[];
const NON_EMPTY_REF_ARRAY: &[u32; 3] = &[1, 2, 3];
fn test_from_const() {
let _ = EMPTY_STR.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_STR.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_BSTR.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_BSTR.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = EMPTY_ARRAY_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = EMPTY_U8_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_U8_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_ARRAY_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_REF_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_REF_ARRAY.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = EMPTY_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = NON_EMPTY_SLICE.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = NON_EMPTY_SLICE_REPEAT.is_empty();
//~^ ERROR: this expression always evaluates to false
}
fn main() {
let value = "foobar";
let _ = value.is_empty();
//~^ ERROR: this expression always evaluates to false
let x = value;
let _ = x.is_empty();
//~^ ERROR: this expression always evaluates to false
let _ = "".is_empty();
//~^ ERROR: this expression always evaluates to true
let _ = b"".is_empty();
//~^ ERROR: this expression always evaluates to true
}
fn str_from_arg(var: &str) {
var.is_empty();
// Do not lint, we know nothiny about var
}
fn update_str() {
let mut value = "duck";
value = "penguin";
let _ = value.is_empty();
// Do not lint since value is mutable
}
fn macros() {
// Content from Macro
let file = include_str!("const_is_empty.rs");
let _ = file.is_empty();
// No lint because initializer comes from a macro result
let var = env!("PATH");
let _ = var.is_empty();
// No lint because initializer comes from a macro result
}
fn conditional_value() {
let value;
if true {
value = "hey";
} else {
value = "hej";
}
let _ = value.is_empty();
// Do not lint, current constant folding is too simple to detect this
}
fn cfg_conditioned() {
#[cfg(test)]
let val = "";
#[cfg(not(test))]
let val = "foo";
let _ = val.is_empty();
// Do not lint, value depend on a #[cfg(…)] directive
}
fn not_cfg_conditioned() {
let val = "";
#[cfg(not(target_os = "inexistent"))]
let _ = val.is_empty();
//~^ ERROR: this expression always evaluates to true
}
const fn const_rand() -> &'static str {
"17"
}
fn const_expressions() {
let _ = const { if true { "1" } else { "2" } }.is_empty();
// Do not lint, we do not recurse into boolean expressions
let _ = const_rand().is_empty();
// Do not lint, we do not recurse into functions
}
fn constant_from_external_crate() {
let _ = std::env::consts::EXE_EXTENSION.is_empty();
// Do not lint, `exe_ext` comes from the `std` crate
}

View File

@ -0,0 +1,161 @@
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:6:8
|
LL | if "".is_empty() {
| ^^^^^^^^^^^^^
|
= note: `-D clippy::const-is-empty` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::const_is_empty)]`
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:9:8
|
LL | if "foobar".is_empty() {
| ^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:15:8
|
LL | if b"".is_empty() {
| ^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:18:8
|
LL | if b"foobar".is_empty() {
| ^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:35:8
|
LL | if empty2.is_empty() {
| ^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:38:8
|
LL | if non_empty2.is_empty() {
| ^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:60:13
|
LL | let _ = EMPTY_STR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:62:13
|
LL | let _ = NON_EMPTY_STR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:64:13
|
LL | let _ = EMPTY_BSTR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:66:13
|
LL | let _ = NON_EMPTY_BSTR.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:68:13
|
LL | let _ = EMPTY_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:70:13
|
LL | let _ = EMPTY_ARRAY_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:72:13
|
LL | let _ = EMPTY_U8_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:74:13
|
LL | let _ = NON_EMPTY_U8_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:76:13
|
LL | let _ = NON_EMPTY_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:78:13
|
LL | let _ = NON_EMPTY_ARRAY_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:80:13
|
LL | let _ = EMPTY_REF_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:82:13
|
LL | let _ = NON_EMPTY_REF_ARRAY.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:84:13
|
LL | let _ = EMPTY_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:86:13
|
LL | let _ = NON_EMPTY_SLICE.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:88:13
|
LL | let _ = NON_EMPTY_SLICE_REPEAT.is_empty();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:94:13
|
LL | let _ = value.is_empty();
| ^^^^^^^^^^^^^^^^
error: this expression always evaluates to false
--> tests/ui/const_is_empty.rs:97:13
|
LL | let _ = x.is_empty();
| ^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:99:13
|
LL | let _ = "".is_empty();
| ^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:101:13
|
LL | let _ = b"".is_empty();
| ^^^^^^^^^^^^^^
error: this expression always evaluates to true
--> tests/ui/const_is_empty.rs:155:13
|
LL | let _ = val.is_empty();
| ^^^^^^^^^^^^^^
error: aborting due to 26 previous errors

View File

@ -1,5 +1,11 @@
#![warn(clippy::len_zero)] #![warn(clippy::len_zero)]
#![allow(dead_code, unused, clippy::needless_if, clippy::len_without_is_empty)] #![allow(
dead_code,
unused,
clippy::needless_if,
clippy::len_without_is_empty,
clippy::const_is_empty
)]
extern crate core; extern crate core;
use core::ops::Deref; use core::ops::Deref;

View File

@ -1,5 +1,11 @@
#![warn(clippy::len_zero)] #![warn(clippy::len_zero)]
#![allow(dead_code, unused, clippy::needless_if, clippy::len_without_is_empty)] #![allow(
dead_code,
unused,
clippy::needless_if,
clippy::len_without_is_empty,
clippy::const_is_empty
)]
extern crate core; extern crate core;
use core::ops::Deref; use core::ops::Deref;

View File

@ -1,5 +1,5 @@
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:82:8 --> tests/ui/len_zero.rs:88:8
| |
LL | if x.len() == 0 { LL | if x.len() == 0 {
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `x.is_empty()` | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `x.is_empty()`
@ -8,13 +8,13 @@ LL | if x.len() == 0 {
= help: to override `-D warnings` add `#[allow(clippy::len_zero)]` = help: to override `-D warnings` add `#[allow(clippy::len_zero)]`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:86:8 --> tests/ui/len_zero.rs:92:8
| |
LL | if "".len() == 0 {} LL | if "".len() == 0 {}
| ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `"".is_empty()` | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `"".is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:95:20 --> tests/ui/len_zero.rs:101:20
| |
LL | println!("{}", *s1 == ""); LL | println!("{}", *s1 == "");
| ^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s1.is_empty()` | ^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s1.is_empty()`
@ -23,121 +23,121 @@ LL | println!("{}", *s1 == "");
= help: to override `-D warnings` add `#[allow(clippy::comparison_to_empty)]` = help: to override `-D warnings` add `#[allow(clippy::comparison_to_empty)]`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:96:20 --> tests/ui/len_zero.rs:102:20
| |
LL | println!("{}", **s2 == ""); LL | println!("{}", **s2 == "");
| ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s2.is_empty()` | ^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s2.is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:97:20 --> tests/ui/len_zero.rs:103:20
| |
LL | println!("{}", ***s3 == ""); LL | println!("{}", ***s3 == "");
| ^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s3.is_empty()` | ^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s3.is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:98:20 --> tests/ui/len_zero.rs:104:20
| |
LL | println!("{}", ****s4 == ""); LL | println!("{}", ****s4 == "");
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s4.is_empty()` | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s4.is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:99:20 --> tests/ui/len_zero.rs:105:20
| |
LL | println!("{}", *****s5 == ""); LL | println!("{}", *****s5 == "");
| ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s5.is_empty()` | ^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `s5.is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:100:20 --> tests/ui/len_zero.rs:106:20
| |
LL | println!("{}", ******(s6) == ""); LL | println!("{}", ******(s6) == "");
| ^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(s6).is_empty()` | ^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(s6).is_empty()`
error: comparison to empty slice error: comparison to empty slice
--> tests/ui/len_zero.rs:103:20 --> tests/ui/len_zero.rs:109:20
| |
LL | println!("{}", &**d2s == ""); LL | println!("{}", &**d2s == "");
| ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(**d2s).is_empty()` | ^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `(**d2s).is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:118:8 --> tests/ui/len_zero.rs:124:8
| |
LL | if has_is_empty.len() == 0 { LL | if has_is_empty.len() == 0 {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:121:8 --> tests/ui/len_zero.rs:127:8
| |
LL | if has_is_empty.len() != 0 { LL | if has_is_empty.len() != 0 {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:124:8 --> tests/ui/len_zero.rs:130:8
| |
LL | if has_is_empty.len() > 0 { LL | if has_is_empty.len() > 0 {
| ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to one error: length comparison to one
--> tests/ui/len_zero.rs:127:8 --> tests/ui/len_zero.rs:133:8
| |
LL | if has_is_empty.len() < 1 { LL | if has_is_empty.len() < 1 {
| ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()`
error: length comparison to one error: length comparison to one
--> tests/ui/len_zero.rs:130:8 --> tests/ui/len_zero.rs:136:8
| |
LL | if has_is_empty.len() >= 1 { LL | if has_is_empty.len() >= 1 {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:141:8 --> tests/ui/len_zero.rs:147:8
| |
LL | if 0 == has_is_empty.len() { LL | if 0 == has_is_empty.len() {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:144:8 --> tests/ui/len_zero.rs:150:8
| |
LL | if 0 != has_is_empty.len() { LL | if 0 != has_is_empty.len() {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:147:8 --> tests/ui/len_zero.rs:153:8
| |
LL | if 0 < has_is_empty.len() { LL | if 0 < has_is_empty.len() {
| ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to one error: length comparison to one
--> tests/ui/len_zero.rs:150:8 --> tests/ui/len_zero.rs:156:8
| |
LL | if 1 <= has_is_empty.len() { LL | if 1 <= has_is_empty.len() {
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to one error: length comparison to one
--> tests/ui/len_zero.rs:153:8 --> tests/ui/len_zero.rs:159:8
| |
LL | if 1 > has_is_empty.len() { LL | if 1 > has_is_empty.len() {
| ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:167:8 --> tests/ui/len_zero.rs:173:8
| |
LL | if with_is_empty.len() == 0 { LL | if with_is_empty.len() == 0 {
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `with_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `with_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:179:6 --> tests/ui/len_zero.rs:185:6
| |
LL | (has_is_empty.len() > 0).then(|| println!("This can happen.")); LL | (has_is_empty.len() > 0).then(|| println!("This can happen."));
| ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:180:6 --> tests/ui/len_zero.rs:186:6
| |
LL | (has_is_empty.len() == 0).then(|| println!("Or this!")); LL | (has_is_empty.len() == 0).then(|| println!("Or this!"));
| ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()` | ^^^^^^^^^^^^^^^^^^^^^^^ help: using `is_empty` is clearer and more explicit: `has_is_empty.is_empty()`
error: length comparison to zero error: length comparison to zero
--> tests/ui/len_zero.rs:184:8 --> tests/ui/len_zero.rs:190:8
| |
LL | if b.len() != 0 {} LL | if b.len() != 0 {}
| ^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!b.is_empty()` | ^^^^^^^^^^^^ help: using `!is_empty` is clearer and more explicit: `!b.is_empty()`

View File

@ -1,4 +1,5 @@
#![warn(clippy::needless_bitwise_bool)] #![warn(clippy::needless_bitwise_bool)]
#![allow(clippy::const_is_empty)]
fn returns_bool() -> bool { fn returns_bool() -> bool {
true true

View File

@ -1,4 +1,5 @@
#![warn(clippy::needless_bitwise_bool)] #![warn(clippy::needless_bitwise_bool)]
#![allow(clippy::const_is_empty)]
fn returns_bool() -> bool { fn returns_bool() -> bool {
true true

View File

@ -1,5 +1,5 @@
error: use of bitwise operator instead of lazy operator between booleans error: use of bitwise operator instead of lazy operator between booleans
--> tests/ui/needless_bitwise_bool.rs:22:8 --> tests/ui/needless_bitwise_bool.rs:23:8
| |
LL | if y & !x { LL | if y & !x {
| ^^^^^^ help: try: `y && !x` | ^^^^^^ help: try: `y && !x`

View File

@ -1,4 +1,5 @@
#![warn(clippy::redundant_as_str)] #![warn(clippy::redundant_as_str)]
#![allow(clippy::const_is_empty)]
fn main() { fn main() {
let string = "Hello, world!".to_owned(); let string = "Hello, world!".to_owned();

View File

@ -1,4 +1,5 @@
#![warn(clippy::redundant_as_str)] #![warn(clippy::redundant_as_str)]
#![allow(clippy::const_is_empty)]
fn main() { fn main() {
let string = "Hello, world!".to_owned(); let string = "Hello, world!".to_owned();

View File

@ -1,5 +1,5 @@
error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too
--> tests/ui/redundant_as_str.rs:7:29 --> tests/ui/redundant_as_str.rs:8:29
| |
LL | let _redundant = string.as_str().as_bytes(); LL | let _redundant = string.as_str().as_bytes();
| ^^^^^^^^^^^^^^^^^ help: try: `as_bytes` | ^^^^^^^^^^^^^^^^^ help: try: `as_bytes`
@ -8,7 +8,7 @@ LL | let _redundant = string.as_str().as_bytes();
= help: to override `-D warnings` add `#[allow(clippy::redundant_as_str)]` = help: to override `-D warnings` add `#[allow(clippy::redundant_as_str)]`
error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too error: this `as_str` is redundant and can be removed as the method immediately following exists on `String` too
--> tests/ui/redundant_as_str.rs:8:29 --> tests/ui/redundant_as_str.rs:9:29
| |
LL | let _redundant = string.as_str().is_empty(); LL | let _redundant = string.as_str().is_empty();
| ^^^^^^^^^^^^^^^^^ help: try: `is_empty` | ^^^^^^^^^^^^^^^^^ help: try: `is_empty`