Merge commit '1e8fdf4928' into clippyup

This commit is contained in:
Philipp Krones 2023-08-11 14:05:13 +02:00
parent a1ab2d765f
commit f730a2655a
72 changed files with 1543 additions and 378 deletions

View file

@ -1,9 +1,7 @@
use std::borrow::Cow;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Mutability, TyKind};
use rustc_lint::LateContext;
@ -16,33 +14,41 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) {
return;
}
if_chain! {
if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind;
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr));
if let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind();
if let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind();
if matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut));
if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind
&& let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr))
&& let ty::RawPtr(TypeAndMut { mutbl: from_mutbl, .. }) = cast_from.kind()
&& let ty::RawPtr(TypeAndMut { ty: to_pointee_ty, mutbl: to_mutbl }) = cast_to.kind()
&& matches!((from_mutbl, to_mutbl),
(Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut))
// The `U` in `pointer::cast` have to be `Sized`
// as explained here: https://github.com/rust-lang/rust/issues/60602.
if to_pointee_ty.is_sized(cx.tcx, cx.param_env);
then {
let mut applicability = Applicability::MachineApplicable;
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut applicability);
let turbofish = match &cast_to_hir_ty.kind {
TyKind::Infer => Cow::Borrowed(""),
TyKind::Ptr(mut_ty) if matches!(mut_ty.ty.kind, TyKind::Infer) => Cow::Borrowed(""),
_ => Cow::Owned(format!("::<{to_pointee_ty}>")),
};
span_lint_and_sugg(
cx,
PTR_AS_PTR,
expr.span,
"`as` casting between raw pointers without changing its mutability",
"try `pointer::cast`, a safer alternative",
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
applicability,
);
}
&& to_pointee_ty.is_sized(cx.tcx, cx.param_env)
{
let mut app = Applicability::MachineApplicable;
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
let turbofish = match &cast_to_hir_ty.kind {
TyKind::Infer => String::new(),
TyKind::Ptr(mut_ty) => {
if matches!(mut_ty.ty.kind, TyKind::Infer) {
String::new()
} else {
format!(
"::<{}>",
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
)
}
},
_ => return,
};
span_lint_and_sugg(
cx,
PTR_AS_PTR,
expr.span,
"`as` casting between raw pointers without changing its mutability",
"try `pointer::cast`, a safer alternative",
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()),
app,
);
}
}

View file

@ -203,6 +203,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::if_let_mutex::IF_LET_MUTEX_INFO,
crate::if_not_else::IF_NOT_ELSE_INFO,
crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO,
crate::ignored_unit_patterns::IGNORED_UNIT_PATTERNS_INFO,
crate::implicit_hasher::IMPLICIT_HASHER_INFO,
crate::implicit_return::IMPLICIT_RETURN_INFO,
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
@ -337,6 +338,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::EXPECT_USED_INFO,
crate::methods::EXTEND_WITH_DRAIN_INFO,
crate::methods::FILETYPE_IS_FILE_INFO,
crate::methods::FILTER_MAP_BOOL_THEN_INFO,
crate::methods::FILTER_MAP_IDENTITY_INFO,
crate::methods::FILTER_MAP_NEXT_INFO,
crate::methods::FILTER_NEXT_INFO,
@ -516,6 +518,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::operators::FLOAT_CMP_CONST_INFO,
crate::operators::FLOAT_EQUALITY_WITHOUT_ABS_INFO,
crate::operators::IDENTITY_OP_INFO,
crate::operators::IMPOSSIBLE_COMPARISONS_INFO,
crate::operators::INEFFECTIVE_BIT_MASK_INFO,
crate::operators::INTEGER_DIVISION_INFO,
crate::operators::MISREFACTORED_ASSIGN_OP_INFO,
@ -524,6 +527,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::operators::NEEDLESS_BITWISE_BOOL_INFO,
crate::operators::OP_REF_INFO,
crate::operators::PTR_EQ_INFO,
crate::operators::REDUNDANT_COMPARISONS_INFO,
crate::operators::SELF_ASSIGNMENT_INFO,
crate::operators::VERBOSE_BIT_MASK_INFO,
crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO,

View file

@ -1,6 +1,4 @@
use clippy_utils::diagnostics::{
span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
};
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{is_lint_allowed, match_def_path, paths};
use if_chain::if_chain;
@ -8,15 +6,14 @@ use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
use rustc_hir::{
self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind,
UnsafeSource, Unsafety,
self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, UnsafeSource, Unsafety,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::traits::Reveal;
use rustc_middle::ty::{
self, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv,
ToPredicate, TraitPredicate, Ty, TyCtxt,
self, ClauseKind, GenericArgKind, GenericParamDefKind, ImplPolarity, ParamEnv, ToPredicate, TraitPredicate, Ty,
TyCtxt,
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
@ -207,10 +204,13 @@ declare_lint_pass!(Derive => [
impl<'tcx> LateLintPass<'tcx> for Derive {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), .. }) = item.kind {
if let ItemKind::Impl(Impl {
of_trait: Some(ref trait_ref),
..
}) = item.kind
{
let ty = cx.tcx.type_of(item.owner_id).instantiate_identity();
let is_automatically_derived =
cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
@ -327,12 +327,7 @@ fn check_ord_partial_ord<'tcx>(
}
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(
cx: &LateContext<'tcx>,
item: &Item<'_>,
trait_ref: &hir::TraitRef<'_>,
ty: Ty<'tcx>,
) {
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
let clone_id = match cx.tcx.lang_items().clone_trait() {
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
_ => return,
@ -350,9 +345,10 @@ fn check_copy_clone<'tcx>(
if !is_copy(cx, ty) {
if ty_subs.non_erasable_generics().next().is_some() {
let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(&copy_id).map_or(false, |impls| {
impls
.iter()
.any(|&id| matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
impls.iter().any(|&id| {
matches!(cx.tcx.type_of(id).instantiate_identity().kind(), ty::Adt(adt, _)
if ty_adt.did() == adt.did())
})
});
if !has_copy_impl {
return;
@ -431,14 +427,7 @@ struct UnsafeVisitor<'a, 'tcx> {
impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_fn(
&mut self,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
body_id: BodyId,
_: Span,
id: LocalDefId,
) {
fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) {
if self.has_unsafe {
return;
}
@ -474,12 +463,7 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
}
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
fn check_partial_eq_without_eq<'tcx>(
cx: &LateContext<'tcx>,
span: Span,
trait_ref: &hir::TraitRef<'_>,
ty: Ty<'tcx>,
) {
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
if_chain! {
if let ty::Adt(adt, args) = ty.kind();
if cx.tcx.visibility(adt.did()).is_public();

View file

@ -716,10 +716,7 @@ fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle =
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
let emitter = EmitterWriter::new(
Box::new(io::sink()),
fallback_bundle,
);
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
let sess = ParseSess::with_span_handler(handler, sm);

View file

@ -10,8 +10,8 @@ use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPat
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind,
GenericArgsRef, ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults,
};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;

View file

@ -0,0 +1,52 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use hir::PatKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `_` in patterns of type `()`.
///
/// ### Why is this bad?
/// Matching with `()` explicitly instead of `_` outlines
/// the fact that the pattern contains no data. Also it
/// would detect a type change that `_` would ignore.
///
/// ### Example
/// ```rust
/// match std::fs::create_dir("tmp-work-dir") {
/// Ok(_) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// }
/// ```
/// Use instead:
/// ```rust
/// match std::fs::create_dir("tmp-work-dir") {
/// Ok(()) => println!("Working directory created"),
/// Err(s) => eprintln!("Could not create directory: {s}"),
/// }
/// ```
#[clippy::version = "1.73.0"]
pub IGNORED_UNIT_PATTERNS,
pedantic,
"suggest replacing `_` by `()` in patterns where appropriate"
}
declare_lint_pass!(IgnoredUnitPatterns => [IGNORED_UNIT_PATTERNS]);
impl<'tcx> LateLintPass<'tcx> for IgnoredUnitPatterns {
fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx hir::Pat<'tcx>) {
if matches!(pat.kind, PatKind::Wild) && cx.typeck_results().pat_ty(pat).is_unit() {
span_lint_and_sugg(
cx,
IGNORED_UNIT_PATTERNS,
pat.span,
"matching over `()` is more explicit",
"use `()` instead of `_`",
String::from("()"),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -147,6 +147,7 @@ mod future_not_send;
mod if_let_mutex;
mod if_not_else;
mod if_then_some_else_none;
mod ignored_unit_patterns;
mod implicit_hasher;
mod implicit_return;
mod implicit_saturating_add;
@ -1093,6 +1094,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
})
});
store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals));
store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View file

@ -162,7 +162,9 @@ fn never_loop_expr<'tcx>(
ExprKind::Binary(_, e1, e2)
| ExprKind::Assign(e1, e2, _)
| ExprKind::AssignOp(_, e1, e2)
| ExprKind::Index(e1, e2, _) => never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id),
| ExprKind::Index(e1, e2, _) => {
never_loop_expr_all(cx, &mut [e1, e2].iter().copied(), ignore_ids, main_loop_id)
},
ExprKind::Loop(b, _, _, _) => {
// Break can come from the inner loop so remove them.
absorb_break(never_loop_block(cx, b, ignore_ids, main_loop_id))

View file

@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if !in_external_macro(cx.sess(), expr.span)
&& (
matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst)
|| cx.tcx.features().active(sym!(const_float_classify))
) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind
&& let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind

View file

@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_to_local;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::visitors::{for_each_expr, is_local_used};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, Guard, MatchSource, Node, Pat, PatKind};
@ -160,6 +161,11 @@ fn emit_redundant_guards<'tcx>(
}
/// Checks if the given `Expr` can also be represented as a `Pat`.
///
/// All literals generally also work as patterns, however float literals are special.
/// They are currently (as of 2023/08/08) still allowed in patterns, but that will become
/// an error in the future, and rustc already actively warns against this (see rust#41620),
/// so we don't consider those as usable within patterns for linting purposes.
fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
for_each_expr(expr, |expr| {
if match expr.kind {
@ -177,8 +183,8 @@ fn expr_can_be_pat(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
ExprKind::AddrOf(..)
| ExprKind::Array(..)
| ExprKind::Tup(..)
| ExprKind::Struct(..)
| ExprKind::Lit(..) => true,
| ExprKind::Struct(..) => true,
ExprKind::Lit(lit) if !matches!(lit.node, LitKind::Float(..)) => true,
_ => false,
} {
return ControlFlow::Continue(());

View file

@ -1,44 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::EXPECT_USED;
/// lint use of `expect()` or `expect_err` for `Result` and `expect()` for `Option`.
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
recv: &hir::Expr<'_>,
is_err: bool,
allow_expect_in_tests: bool,
) {
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
Some((EXPECT_USED, "an `Option`", "None", ""))
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
Some((EXPECT_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
} else {
None
};
let method = if is_err { "expect_err" } else { "expect" };
if allow_expect_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) {
return;
}
if let Some((lint, kind, none_value, none_prefix)) = mess {
span_lint_and_help(
cx,
lint,
expr.span,
&format!("used `{method}()` on {kind} value"),
None,
&format!("if this value is {none_prefix}`{none_value}`, it will panic"),
);
}
}

View file

@ -0,0 +1,53 @@
use super::FILTER_MAP_BOOL_THEN;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths::BOOL_THEN;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::is_copy;
use clippy_utils::{is_from_proc_macro, is_trait_method, match_def_path, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Binder;
use rustc_span::{sym, Span};
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: &Expr<'_>, call_span: Span) {
if !in_external_macro(cx.sess(), expr.span)
&& is_trait_method(cx, expr, sym::Iterator)
&& let ExprKind::Closure(closure) = arg.kind
&& let body = cx.tcx.hir().body(closure.body)
&& let value = peel_blocks(body.value)
// Indexing should be fine as `filter_map` always has 1 input, we unfortunately need both
// `inputs` and `params` here as we need both the type and the span
&& let param_ty = closure.fn_decl.inputs[0]
&& let param = body.params[0]
// Issue #11309
&& let param_ty = cx.tcx.liberate_late_bound_regions(
closure.def_id.to_def_id(),
Binder::bind_with_vars(
cx.typeck_results().node_type(param_ty.hir_id),
cx.tcx.late_bound_vars(cx.tcx.hir().local_def_id_to_hir_id(closure.def_id)),
),
)
&& is_copy(cx, param_ty)
&& let ExprKind::MethodCall(_, recv, [then_arg], _) = value.kind
&& let ExprKind::Closure(then_closure) = then_arg.kind
&& let then_body = peel_blocks(cx.tcx.hir().body(then_closure.body).value)
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(value.hir_id)
&& match_def_path(cx, def_id, &BOOL_THEN)
&& !is_from_proc_macro(cx, expr)
&& let Some(param_snippet) = snippet_opt(cx, param.span)
&& let Some(filter) = snippet_opt(cx, recv.span)
&& let Some(map) = snippet_opt(cx, then_body.span)
{
span_lint_and_sugg(
cx,
FILTER_MAP_BOOL_THEN,
call_span,
"usage of `bool::then` in `filter_map`",
"use `filter` then `map` instead",
format!("filter(|&{param_snippet}| {filter}).map(|{param_snippet}| {map})"),
Applicability::MachineApplicable,
);
}
}

View file

@ -17,10 +17,10 @@ mod collapsible_str_replace;
mod drain_collect;
mod err_expect;
mod expect_fun_call;
mod expect_used;
mod extend_with_drain;
mod filetype_is_file;
mod filter_map;
mod filter_map_bool_then;
mod filter_map_identity;
mod filter_map_next;
mod filter_next;
@ -104,7 +104,7 @@ mod unnecessary_lazy_eval;
mod unnecessary_literal_unwrap;
mod unnecessary_sort_by;
mod unnecessary_to_owned;
mod unwrap_used;
mod unwrap_expect_used;
mod useless_asref;
mod utils;
mod vec_resize_to_zero;
@ -3476,6 +3476,37 @@ declare_clippy_lint! {
"disallows `.skip(0)`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `bool::then` in `Iterator::filter_map`.
///
/// ### Why is this bad?
/// This can be written with `filter` then `map` instead, which would reduce nesting and
/// separates the filtering from the transformation phase. This comes with no cost to
/// performance and is just cleaner.
///
/// ### Limitations
/// Does not lint `bool::then_some`, as it eagerly evaluates its arguments rather than lazily.
/// This can create differing behavior, so better safe than sorry.
///
/// ### Example
/// ```rust
/// # fn really_expensive_fn(i: i32) -> i32 { i }
/// # let v = vec![];
/// _ = v.into_iter().filter_map(|i| (i % 2 == 0).then(|| really_expensive_fn(i)));
/// ```
/// Use instead:
/// ```rust
/// # fn really_expensive_fn(i: i32) -> i32 { i }
/// # let v = vec![];
/// _ = v.into_iter().filter(|i| i % 2 == 0).map(|i| really_expensive_fn(i));
/// ```
#[clippy::version = "1.72.0"]
pub FILTER_MAP_BOOL_THEN,
style,
"checks for usage of `bool::then` in `Iterator::filter_map`"
}
declare_clippy_lint! {
/// ### What it does
/// Looks for calls to `RwLock::write` where the lock is only used for reading.
@ -3644,6 +3675,7 @@ impl_lint_pass!(Methods => [
FORMAT_COLLECT,
STRING_LIT_CHARS_ANY,
ITER_SKIP_ZERO,
FILTER_MAP_BOOL_THEN,
READONLY_WRITE_LOCK
]);
@ -3848,6 +3880,13 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, arg, "and");
}
},
("any", [arg]) if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params
&& let Some(("chars", recv, _, _, _)) = method_call(recv) =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
}
("arg", [arg]) => {
suspicious_command_arg_space::check(cx, recv, arg, span);
}
@ -3908,13 +3947,27 @@ impl Methods {
match method_call(recv) {
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
_ => unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
),
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("expect_err", [_]) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_expect_in_tests,
unwrap_expect_used::Variant::Expect,
);
},
("extend", [arg]) => {
string_extend_chars::check(cx, expr, recv, arg);
@ -3922,6 +3975,7 @@ impl Methods {
},
("filter_map", [arg]) => {
unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_bool_then::check(cx, expr, arg, call_span);
filter_map_identity::check(cx, expr, arg, span);
},
("find_map", [arg]) => {
@ -3965,20 +4019,9 @@ impl Methods {
unnecessary_join::check(cx, expr, recv, join_arg, span);
}
},
("skip", [arg]) => {
iter_skip_zero::check(cx, expr, arg);
if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
if let ("cloned", []) = (name2, args2) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
}
}
("last", []) => {
if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
if let ("cloned", []) = (name2, args2) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
},
("lock", []) => {
@ -4026,13 +4069,6 @@ impl Methods {
}
}
},
("any", [arg]) if let ExprKind::Closure(arg) = arg.kind
&& let body = cx.tcx.hir().body(arg.body)
&& let [param] = body.params
&& let Some(("chars", recv, _, _, _)) = method_call(recv) =>
{
string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv);
}
("nth", [n_arg]) => match method_call(recv) {
Some(("bytes", recv2, [], _, _)) => bytes_nth::check(cx, expr, recv2, n_arg),
Some(("cloned", recv2, [], _, _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false),
@ -4086,6 +4122,13 @@ impl Methods {
seek_to_start_instead_of_rewind::check(cx, expr, recv, arg, span);
}
},
("skip", [arg]) => {
iter_skip_zero::check(cx, expr, arg);
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
}
("sort", []) => {
stable_sort_primitive::check(cx, expr, recv);
},
@ -4108,10 +4151,8 @@ impl Methods {
},
("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg),
("take", [_arg]) => {
if let Some((name2, recv2, args2, _span2, _)) = method_call(recv) {
if let ("cloned", []) = (name2, args2) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) {
iter_overeager_cloned::check(cx, expr, recv, recv2, false, false);
}
},
("take", []) => needless_option_take::check(cx, expr, recv),
@ -4146,11 +4187,25 @@ impl Methods {
_ => {},
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests);
unwrap_expect_used::check(
cx,
expr,
recv,
false,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_err", []) => {
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests);
unwrap_expect_used::check(
cx,
expr,
recv,
true,
self.allow_unwrap_in_tests,
unwrap_expect_used::Variant::Unwrap,
);
},
("unwrap_or", [u_arg]) => {
match method_call(recv) {
@ -4180,6 +4235,9 @@ impl Methods {
}
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
},
("write", []) => {
readonly_write_lock::check(cx, expr, recv);
}
("zip", [arg]) => {
if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind
&& name.ident.name == sym::iter
@ -4187,9 +4245,6 @@ impl Methods {
range_zip_with_len::check(cx, expr, iter_recv, arg);
}
},
("write", []) => {
readonly_write_lock::check(cx, expr, recv);
}
_ => {},
}
}

View file

@ -0,0 +1,83 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{is_never_like, is_type_diagnostic_item};
use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed};
use rustc_hir::Expr;
use rustc_lint::{LateContext, Lint};
use rustc_middle::ty;
use rustc_span::sym;
use super::{EXPECT_USED, UNWRAP_USED};
#[derive(Clone, Copy, Eq, PartialEq)]
pub(super) enum Variant {
Unwrap,
Expect,
}
impl Variant {
fn method_name(self, is_err: bool) -> &'static str {
match (self, is_err) {
(Variant::Unwrap, true) => "unwrap_err",
(Variant::Unwrap, false) => "unwrap",
(Variant::Expect, true) => "expect_err",
(Variant::Expect, false) => "expect",
}
}
fn lint(self) -> &'static Lint {
match self {
Variant::Unwrap => UNWRAP_USED,
Variant::Expect => EXPECT_USED,
}
}
}
/// Lint usage of `unwrap` or `unwrap_err` for `Result` and `unwrap()` for `Option` (and their
/// `expect` counterparts).
pub(super) fn check(
cx: &LateContext<'_>,
expr: &Expr<'_>,
recv: &Expr<'_>,
is_err: bool,
allow_unwrap_in_tests: bool,
variant: Variant,
) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
let (kind, none_value, none_prefix) = if is_type_diagnostic_item(cx, ty, sym::Option) && !is_err {
("an `Option`", "None", "")
} else if is_type_diagnostic_item(cx, ty, sym::Result)
&& let ty::Adt(_, substs) = ty.kind()
&& let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type()
{
if is_never_like(t_or_e_ty) {
return;
}
("a `Result`", if is_err { "Ok" } else { "Err" }, "an ")
} else {
return;
};
let method_suffix = if is_err { "_err" } else { "" };
if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) {
return;
}
span_lint_and_then(
cx,
variant.lint(),
expr.span,
&format!("used `{}()` on {kind} value", variant.method_name(is_err)),
|diag| {
diag.note(format!("if this value is {none_prefix}`{none_value}`, it will panic"));
if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) {
diag.help(format!(
"consider using `expect{method_suffix}()` to provide a better panic message"
));
}
},
);
}

View file

@ -1,53 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed};
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::{EXPECT_USED, UNWRAP_USED};
/// lint use of `unwrap()` or `unwrap_err` for `Result` and `unwrap()` for `Option`.
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
recv: &hir::Expr<'_>,
is_err: bool,
allow_unwrap_in_tests: bool,
) {
let obj_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let mess = if is_type_diagnostic_item(cx, obj_ty, sym::Option) && !is_err {
Some((UNWRAP_USED, "an `Option`", "None", ""))
} else if is_type_diagnostic_item(cx, obj_ty, sym::Result) {
Some((UNWRAP_USED, "a `Result`", if is_err { "Ok" } else { "Err" }, "an "))
} else {
None
};
let method_suffix = if is_err { "_err" } else { "" };
if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) {
return;
}
if let Some((lint, kind, none_value, none_prefix)) = mess {
let help = if is_lint_allowed(cx, EXPECT_USED, expr.hir_id) {
format!(
"if you don't want to handle the `{none_value}` case gracefully, consider \
using `expect{method_suffix}()` to provide a better panic message"
)
} else {
format!("if this value is {none_prefix}`{none_value}`, it will panic")
};
span_lint_and_help(
cx,
lint,
expr.span,
&format!("used `unwrap{method_suffix}()` on {kind} value"),
None,
&help,
);
}
}

View file

@ -37,6 +37,11 @@ declare_lint_pass!(UnnecessaryMutPassed => [UNNECESSARY_MUT_PASSED]);
impl<'tcx> LateLintPass<'tcx> for UnnecessaryMutPassed {
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
if e.span.from_expansion() {
// Issue #11268
return;
}
match e.kind {
ExprKind::Call(fn_expr, arguments) => {
if let ExprKind::Path(ref path) = fn_expr.kind {

View file

@ -0,0 +1,207 @@
#![allow(clippy::match_same_arms)]
use std::cmp::Ordering;
use clippy_utils::consts::{constant, Constant};
use if_chain::if_chain;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::layout::HasTyCtxt;
use rustc_middle::ty::{Ty, TypeckResults};
use rustc_span::source_map::{Span, Spanned};
use clippy_utils::diagnostics::span_lint_and_note;
use clippy_utils::source::snippet;
use clippy_utils::SpanlessEq;
use super::{IMPOSSIBLE_COMPARISONS, REDUNDANT_COMPARISONS};
// Extract a comparison between a const and non-const
// Flip yoda conditionals, turnings expressions like `42 < x` into `x > 42`
fn comparison_to_const<'tcx>(
cx: &LateContext<'tcx>,
typeck: &TypeckResults<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> Option<(CmpOp, &'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Constant<'tcx>, Ty<'tcx>)> {
if_chain! {
if let ExprKind::Binary(operator, left, right) = expr.kind;
if let Ok(cmp_op) = CmpOp::try_from(operator.node);
then {
match (constant(cx, typeck, left), constant(cx, typeck, right)) {
(Some(_), Some(_)) => None,
(_, Some(con)) => Some((cmp_op, left, right, con, typeck.expr_ty(right))),
(Some(con), _) => Some((cmp_op.reverse(), right, left, con, typeck.expr_ty(left))),
_ => None,
}
} else {
None
}
}
}
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
and_op: Spanned<BinOpKind>,
left_cond: &'tcx Expr<'tcx>,
right_cond: &'tcx Expr<'tcx>,
span: Span,
) {
if_chain! {
// Ensure that the binary operator is &&
if and_op.node == BinOpKind::And;
// Check that both operands to '&&' are themselves a binary operation
// The `comparison_to_const` step also checks this, so this step is just an optimization
if let ExprKind::Binary(_, _, _) = left_cond.kind;
if let ExprKind::Binary(_, _, _) = right_cond.kind;
let typeck = cx.typeck_results();
// Check that both operands to '&&' compare a non-literal to a literal
if let Some((left_cmp_op, left_expr, left_const_expr, left_const, left_type)) =
comparison_to_const(cx, typeck, left_cond);
if let Some((right_cmp_op, right_expr, right_const_expr, right_const, right_type)) =
comparison_to_const(cx, typeck, right_cond);
if left_type == right_type;
// Check that the same expression is compared in both comparisons
if SpanlessEq::new(cx).eq_expr(left_expr, right_expr);
if !left_expr.can_have_side_effects();
// Compare the two constant expressions
if let Some(ordering) = Constant::partial_cmp(cx.tcx(), left_type, &left_const, &right_const);
// Rule out the `x >= 42 && x <= 42` corner case immediately
// Mostly to simplify the implementation, but it is also covered by `clippy::double_comparisons`
if !matches!(
(&left_cmp_op, &right_cmp_op, ordering),
(CmpOp::Le | CmpOp::Ge, CmpOp::Le | CmpOp::Ge, Ordering::Equal)
);
then {
if left_cmp_op.direction() == right_cmp_op.direction() {
let lhs_str = snippet(cx, left_cond.span, "<lhs>");
let rhs_str = snippet(cx, right_cond.span, "<rhs>");
// We already know that either side of `&&` has no effect,
// but emit a different error message depending on which side it is
if left_side_is_useless(left_cmp_op, ordering) {
span_lint_and_note(
cx,
REDUNDANT_COMPARISONS,
span,
"left-hand side of `&&` operator has no effect",
Some(left_cond.span.until(right_cond.span)),
&format!("`if `{rhs_str}` evaluates to true, {lhs_str}` will always evaluate to true as well"),
);
} else {
span_lint_and_note(
cx,
REDUNDANT_COMPARISONS,
span,
"right-hand side of `&&` operator has no effect",
Some(and_op.span.to(right_cond.span)),
&format!("`if `{lhs_str}` evaluates to true, {rhs_str}` will always evaluate to true as well"),
);
}
// We could autofix this error but choose not to,
// because code triggering this lint probably not behaving correctly in the first place
}
else if !comparison_is_possible(left_cmp_op.direction(), ordering) {
let expr_str = snippet(cx, left_expr.span, "..");
let lhs_str = snippet(cx, left_const_expr.span, "<lhs>");
let rhs_str = snippet(cx, right_const_expr.span, "<rhs>");
let note = match ordering {
Ordering::Less => format!("since `{lhs_str}` < `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"),
Ordering::Equal => format!("`{expr_str}` cannot simultaneously be greater than and less than `{lhs_str}`"),
Ordering::Greater => format!("since `{lhs_str}` > `{rhs_str}`, the expression evaluates to false for any value of `{expr_str}`"),
};
span_lint_and_note(
cx,
IMPOSSIBLE_COMPARISONS,
span,
"boolean expression will never evaluate to 'true'",
None,
&note,
);
};
}
}
}
fn left_side_is_useless(left_cmp_op: CmpOp, ordering: Ordering) -> bool {
// Special-case for equal constants with an inclusive comparison
if ordering == Ordering::Equal {
match left_cmp_op {
CmpOp::Lt | CmpOp::Gt => false,
CmpOp::Le | CmpOp::Ge => true,
}
} else {
match (left_cmp_op.direction(), ordering) {
(CmpOpDirection::Lesser, Ordering::Less) => false,
(CmpOpDirection::Lesser, Ordering::Equal) => false,
(CmpOpDirection::Lesser, Ordering::Greater) => true,
(CmpOpDirection::Greater, Ordering::Less) => true,
(CmpOpDirection::Greater, Ordering::Equal) => false,
(CmpOpDirection::Greater, Ordering::Greater) => false,
}
}
}
fn comparison_is_possible(left_cmp_direction: CmpOpDirection, ordering: Ordering) -> bool {
match (left_cmp_direction, ordering) {
(CmpOpDirection::Lesser, Ordering::Less | Ordering::Equal) => false,
(CmpOpDirection::Lesser, Ordering::Greater) => true,
(CmpOpDirection::Greater, Ordering::Greater | Ordering::Equal) => false,
(CmpOpDirection::Greater, Ordering::Less) => true,
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum CmpOpDirection {
Lesser,
Greater,
}
#[derive(Clone, Copy)]
enum CmpOp {
Lt,
Le,
Ge,
Gt,
}
impl CmpOp {
fn reverse(self) -> Self {
match self {
CmpOp::Lt => CmpOp::Gt,
CmpOp::Le => CmpOp::Ge,
CmpOp::Ge => CmpOp::Le,
CmpOp::Gt => CmpOp::Lt,
}
}
fn direction(self) -> CmpOpDirection {
match self {
CmpOp::Lt => CmpOpDirection::Lesser,
CmpOp::Le => CmpOpDirection::Lesser,
CmpOp::Ge => CmpOpDirection::Greater,
CmpOp::Gt => CmpOpDirection::Greater,
}
}
}
impl TryFrom<BinOpKind> for CmpOp {
type Error = ();
fn try_from(bin_op: BinOpKind) -> Result<Self, Self::Error> {
match bin_op {
BinOpKind::Lt => Ok(CmpOp::Lt),
BinOpKind::Le => Ok(CmpOp::Le),
BinOpKind::Ge => Ok(CmpOp::Ge),
BinOpKind::Gt => Ok(CmpOp::Gt),
_ => Err(()),
}
}
}

View file

@ -2,6 +2,7 @@ mod absurd_extreme_comparisons;
mod assign_op_pattern;
mod bit_mask;
mod cmp_owned;
mod const_comparisons;
mod double_comparison;
mod duration_subsec;
mod eq_op;
@ -298,6 +299,45 @@ declare_clippy_lint! {
"unnecessary double comparisons that can be simplified"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for double comparisons that can never succeed
///
/// ### Why is this bad?
/// The whole expression can be replaced by `false`,
/// which is probably not the programmer's intention
///
/// ### Example
/// ```rust
/// # let status_code = 200;
/// if status_code <= 400 && status_code > 500 {}
/// ```
#[clippy::version = "1.71.0"]
pub IMPOSSIBLE_COMPARISONS,
correctness,
"double comparisons that will never evaluate to `true`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for ineffective double comparisons against constants.
///
/// ### Why is this bad?
/// Only one of the comparisons has any effect on the result, the programmer
/// probably intended to flip one of the comparison operators, or compare a
/// different value entirely.
///
/// ### Example
/// ```rust
/// # let status_code = 200;
/// if status_code <= 400 && status_code < 500 {}
/// ```
#[clippy::version = "1.71.0"]
pub REDUNDANT_COMPARISONS,
correctness,
"double comparisons where one of them can be removed"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calculation of subsecond microseconds or milliseconds
@ -742,6 +782,8 @@ impl_lint_pass!(Operators => [
INEFFECTIVE_BIT_MASK,
VERBOSE_BIT_MASK,
DOUBLE_COMPARISONS,
IMPOSSIBLE_COMPARISONS,
REDUNDANT_COMPARISONS,
DURATION_SUBSEC,
EQ_OP,
OP_REF,
@ -786,6 +828,7 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
bit_mask::check(cx, e, op.node, lhs, rhs);
verbose_bit_mask::check(cx, e, op.node, lhs, rhs, self.verbose_bit_mask_threshold);
double_comparison::check(cx, op.node, lhs, rhs, e.span);
const_comparisons::check(cx, op, lhs, rhs, e.span);
duration_subsec::check(cx, e, op.node, lhs, rhs);
float_equality_without_abs::check(cx, e, op.node, lhs, rhs);
integer_division::check(cx, e, op.node, lhs, rhs);

View file

@ -155,7 +155,7 @@ fn try_get_option_occurrence<'tcx>(
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind {
match some_captures.get(local_id)
.or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id)))
.or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|()| none_captures.get(local_id)))
{
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None,
Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None,

View file

@ -1,11 +1,13 @@
use crate::manual_let_else::{MatchLintBehaviour, MANUAL_LET_ELSE};
use crate::question_mark_used::QUESTION_MARK_USED;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::Msrv;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_path_lang_item, is_res_lang_ctor,
pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
eq_expr_value, get_parent_node, higher, in_constant, is_else_clause, is_lint_allowed, is_path_lang_item,
is_res_lang_ctor, pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks,
peel_blocks_with_stmt,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
@ -299,13 +301,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &rustc_hir::Block<'_>) -> bool {
impl<'tcx> LateLintPass<'tcx> for QuestionMark {
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) {
return;
}
if !in_constant(cx, stmt.hir_id) {
check_let_some_else_return_none(cx, stmt);
}
self.check_manual_let_else(cx, stmt);
}
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !in_constant(cx, expr.hir_id) {
if !in_constant(cx, expr.hir_id) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id) {
self.check_is_none_or_err_and_early_return(cx, expr);
self.check_if_let_some_or_err_and_early_return(cx, expr);
}

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::is_from_proc_macro;
use clippy_utils::ty::needs_ordered_drop;
use rustc_ast::Mutability;
use rustc_hir::def::Res;
use rustc_hir::{
BindingAnnotation, ByRef, Expr, ExprKind, HirId, Local, Node, Pat, PatKind, QPath,
@ -9,6 +10,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::{in_external_macro, is_from_async_await};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Ident;
use rustc_span::DesugaringKind;
declare_clippy_lint! {
/// ### What it does
@ -47,6 +49,7 @@ declare_lint_pass!(RedundantLocals => [REDUNDANT_LOCALS]);
impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
if_chain! {
if !local.span.is_desugaring(DesugaringKind::Async);
// the pattern is a single by-value binding
if let PatKind::Binding(BindingAnnotation(ByRef::No, mutability), _, ident, None) = local.pat.kind;
// the binding is not type-ascribed
@ -62,6 +65,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id);
// the previous binding has the same mutability
if find_binding(binding_pat, ident).unwrap().1 == mutability;
// the local does not change the effect of assignments to the binding. see #11290
if !affects_assignments(cx, mutability, binding_id, local.hir_id);
// the local does not affect the code's drop behavior
if !affects_drop_behavior(cx, binding_id, local.hir_id, expr);
// the local is user-controlled
@ -97,6 +102,14 @@ fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> {
ret
}
/// Check if a rebinding of a local changes the effect of assignments to the binding.
fn affects_assignments(cx: &LateContext<'_>, mutability: Mutability, bind: HirId, rebind: HirId) -> bool {
let hir = cx.tcx.hir();
// the binding is mutable and the rebinding is in a different scope than the original binding
mutability == Mutability::Mut && hir.get_enclosing_scope(bind) != hir.get_enclosing_scope(rebind)
}
/// Check if a rebinding of a local affects the code's drop behavior.
fn affects_drop_behavior<'tcx>(
cx: &LateContext<'tcx>,

View file

@ -1,6 +1,8 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_lint_allowed;
use rustc_ast::LitKind;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::Ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -45,8 +47,8 @@ fn is_same_type<'tcx>(cx: &LateContext<'tcx>, ty_resolved_path: hir::def::Res, f
return primty.name() == func_return_type_sym;
}
// type annotation is any other non generic type
if let hir::def::Res::Def(_, defid) = ty_resolved_path
// type annotation is a non generic type
if let hir::def::Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, defid) = ty_resolved_path
&& let Some(annotation_ty) = cx.tcx.type_of(defid).no_bound_vars()
{
return annotation_ty == func_return_type;
@ -130,8 +132,9 @@ fn extract_primty(ty_kind: &hir::TyKind<'_>) -> Option<hir::PrimTy> {
impl LateLintPass<'_> for RedundantTypeAnnotations {
fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx rustc_hir::Local<'tcx>) {
// type annotation part
if !local.span.from_expansion()
if !is_lint_allowed(cx, REDUNDANT_TYPE_ANNOTATIONS, local.hir_id)
// type annotation part
&& !local.span.from_expansion()
&& let Some(ty) = &local.ty
// initialization part

View file

@ -20,18 +20,27 @@ declare_clippy_lint! {
/// These structures are non-idiomatic and less efficient than simply using
/// `vec![0; len]`.
///
/// Specifically, for `vec![0; len]`, the compiler can use a specialized type of allocation
/// that also zero-initializes the allocated memory in the same call
/// (see: [alloc_zeroed](https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#method.alloc_zeroed)).
///
/// Writing `Vec::new()` followed by `vec.resize(len, 0)` is suboptimal because,
/// while it does do the same number of allocations,
/// it involves two operations for allocating and initializing.
/// The `resize` call first allocates memory (since `Vec::new()` did not), and only *then* zero-initializes it.
///
/// ### Example
/// ```rust
/// # use core::iter::repeat;
/// # let len = 4;
/// let mut vec1 = Vec::with_capacity(len);
/// let mut vec1 = Vec::new();
/// vec1.resize(len, 0);
///
/// let mut vec1 = Vec::with_capacity(len);
/// vec1.resize(vec1.capacity(), 0);
///
/// let mut vec2 = Vec::with_capacity(len);
/// vec2.extend(repeat(0).take(len));
/// vec2.resize(len, 0);
///
/// let mut vec3 = Vec::with_capacity(len);
/// vec3.extend(repeat(0).take(len));
/// ```
///
/// Use instead:
@ -39,6 +48,7 @@ declare_clippy_lint! {
/// # let len = 4;
/// let mut vec1 = vec![0; len];
/// let mut vec2 = vec![0; len];
/// let mut vec3 = vec![0; len];
/// ```
#[clippy::version = "1.32.0"]
pub SLOW_VECTOR_INITIALIZATION,

View file

@ -1,5 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::numeric_literal::NumericLiteral;
use clippy_utils::source::snippet_with_context;
use clippy_utils::source::snippet;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -28,27 +30,29 @@ declare_lint_pass!(ConfusingXorAndPow => [SUSPICIOUS_XOR_USED_AS_POW]);
impl LateLintPass<'_> for ConfusingXorAndPow {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if !in_external_macro(cx.sess(), expr.span) &&
let ExprKind::Binary(op, left, right) = &expr.kind &&
op.node == BinOpKind::BitXor &&
left.span.ctxt() == right.span.ctxt() &&
let ExprKind::Lit(lit_left) = &left.kind &&
let ExprKind::Lit(lit_right) = &right.kind &&
let snip_left = snippet_with_context(cx, lit_left.span, lit_left.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) &&
let snip_right = snippet_with_context(cx, lit_right.span, lit_right.span.ctxt(), "..", &mut Applicability::MaybeIncorrect) &&
let Some(left_val) = NumericLiteral::from_lit_kind(&snip_left.0, &lit_left.node) &&
let Some(right_val) = NumericLiteral::from_lit_kind(&snip_right.0, &lit_right.node) &&
left_val.is_decimal() &&
right_val.is_decimal() {
clippy_utils::diagnostics::span_lint_and_sugg(
cx,
SUSPICIOUS_XOR_USED_AS_POW,
expr.span,
"`^` is not the exponentiation operator",
"did you mean to write",
format!("{}.pow({})", left_val.format(), right_val.format()),
Applicability::MaybeIncorrect,
);
if !in_external_macro(cx.sess(), expr.span)
&& let ExprKind::Binary(op, left, right) = &expr.kind
&& op.node == BinOpKind::BitXor
&& left.span.ctxt() == right.span.ctxt()
&& let ExprKind::Lit(lit_left) = &left.kind
&& let ExprKind::Lit(lit_right) = &right.kind
&& matches!(lit_right.node, LitKind::Int(..) | LitKind::Float(..))
&& matches!(lit_left.node, LitKind::Int(..) | LitKind::Float(..))
&& NumericLiteral::from_lit_kind(&snippet(cx, lit_right.span, ".."), &lit_right.node).is_some_and(|x| x.is_decimal())
{
span_lint_and_sugg(
cx,
SUSPICIOUS_XOR_USED_AS_POW,
expr.span,
"`^` is not the exponentiation operator",
"did you mean to write",
format!(
"{}.pow({})",
lit_left.node,
lit_right.node
),
Applicability::MaybeIncorrect,
);
}
}
}

View file

@ -34,7 +34,7 @@ impl<'tcx> LateLintPass<'tcx> for InvalidPaths {
let mod_name = &cx.tcx.item_name(local_def_id.to_def_id());
if_chain! {
if mod_name.as_str() == "paths";
if let hir::ItemKind::Const(ty, body_id) = item.kind;
if let hir::ItemKind::Const(ty, _, body_id) = item.kind;
let ty = hir_ty_to_ty(cx.tcx, ty);
if let ty::Array(el_ty, _) = &ty.kind();
if let ty::Ref(_, el_ty, _) = &el_ty.kind();