diff options
| author | Philipp Krones <hello@philkrones.com> | 2023-08-11 14:05:13 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2023-08-11 14:05:13 +0200 |
| commit | f730a2655a0ea22da4cf104d3e97f9fa17c0658f (patch) | |
| tree | a93171179363bddc55403723e0b625e9c9758f91 /clippy_lints | |
| parent | a1ab2d765f8ee3c7735c5573e023f4f4057159e1 (diff) | |
| download | rust-f730a2655a0ea22da4cf104d3e97f9fa17c0658f.tar.gz rust-f730a2655a0ea22da4cf104d3e97f9fa17c0658f.zip | |
Merge commit '1e8fdf492808a25d78a97e1242b835ace9924e4d' into clippyup
Diffstat (limited to 'clippy_lints')
25 files changed, 676 insertions, 238 deletions
diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 15ffb00da88..181dbcf6e9a 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -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, + ); } } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 9a9998cca4a..db114abfc86 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -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, diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 9900dbdee6b..d3311792cfa 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -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(©_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(); diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index 2c4d93e33ba..e29ab634c97 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -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); diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index ba7957b0dec..38066503c07 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -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; diff --git a/clippy_lints/src/ignored_unit_patterns.rs b/clippy_lints/src/ignored_unit_patterns.rs new file mode 100644 index 00000000000..c635120b882 --- /dev/null +++ b/clippy_lints/src/ignored_unit_patterns.rs @@ -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, + ); + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 9d6096ccb2a..358004cf460 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -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` } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index dd77c69ef88..cc19ac55e5e 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -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)) diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index f48a5d9d245..88db7ae6aec 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -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 diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 6383326aa38..29af4812351 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -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(()); diff --git a/clippy_lints/src/methods/expect_used.rs b/clippy_lints/src/methods/expect_used.rs deleted file mode 100644 index 614610335a1..00000000000 --- a/clippy_lints/src/methods/expect_used.rs +++ /dev/null @@ -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"), - ); - } -} diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs new file mode 100644 index 00000000000..fafc9709770 --- /dev/null +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -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, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 28a8978973f..42756b27d01 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -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; @@ -3478,6 +3478,37 @@ declare_clippy_lint! { 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. /// /// ### Why is this bad? @@ -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); - } _ => {}, } } diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs new file mode 100644 index 00000000000..7bd16b473ce --- /dev/null +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -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" + )); + } + }, + ); +} diff --git a/clippy_lints/src/methods/unwrap_used.rs b/clippy_lints/src/methods/unwrap_used.rs deleted file mode 100644 index 5e4c3daee64..00000000000 --- a/clippy_lints/src/methods/unwrap_used.rs +++ /dev/null @@ -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, - ); - } -} diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 4b20aecad4a..e53e146ec5d 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -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 { diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs new file mode 100644 index 00000000000..abe8df19543 --- /dev/null +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -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, + ¬e, + ); + }; + } + } +} + +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(()), + } + } +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 2cf15adda01..4635e1164cd 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -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; @@ -300,6 +301,45 @@ declare_clippy_lint! { 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 /// from other `Duration` methods. /// @@ -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); diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 40da002f4ff..a7a7f4fd8fa 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -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, diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index f5502cffb66..734ca2914f5 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -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); } diff --git a/clippy_lints/src/redundant_locals.rs b/clippy_lints/src/redundant_locals.rs index 896bd79b20b..0c89c7ee47d 100644 --- a/clippy_lints/src/redundant_locals.rs +++ b/clippy_lints/src/redundant_locals.rs @@ -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>, diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 8e9234bba3c..3e963d79892 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -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 diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 54a33eb2986..c9ab622ad25 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -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, diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs index 6fa49afe0ec..8e156b8829b 100644 --- a/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -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, + ); } } } diff --git a/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/clippy_lints/src/utils/internal_lints/invalid_paths.rs index e4906944c8d..4ed985f54d0 100644 --- a/clippy_lints/src/utils/internal_lints/invalid_paths.rs +++ b/clippy_lints/src/utils/internal_lints/invalid_paths.rs @@ -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(); |
