diff options
| author | flip1995 <hello@philkrones.com> | 2021-01-30 18:06:34 +0100 |
|---|---|---|
| committer | flip1995 <hello@philkrones.com> | 2021-01-30 18:06:34 +0100 |
| commit | 21758bf1fbeb0993fe39daf073b4bf883d36e779 (patch) | |
| tree | 24e6ce5e1552a540571c7f92c951254e1e0b2119 /src/tools/clippy/clippy_lints | |
| parent | 7ce1b3b24491cbe10669cbe2b5733c2fe7cfe5b7 (diff) | |
| parent | 95c0459217d1661edfa794c8bb122452b92fb485 (diff) | |
| download | rust-21758bf1fbeb0993fe39daf073b4bf883d36e779.tar.gz rust-21758bf1fbeb0993fe39daf073b4bf883d36e779.zip | |
Merge commit '95c0459217d1661edfa794c8bb122452b92fb485' into clippyup
Diffstat (limited to 'src/tools/clippy/clippy_lints')
35 files changed, 858 insertions, 207 deletions
diff --git a/src/tools/clippy/clippy_lints/src/as_conversions.rs b/src/tools/clippy/clippy_lints/src/as_conversions.rs index 0c8efd75514..c30d65bbc57 100644 --- a/src/tools/clippy/clippy_lints/src/as_conversions.rs +++ b/src/tools/clippy/clippy_lints/src/as_conversions.rs @@ -8,6 +8,14 @@ use crate::utils::span_lint_and_help; declare_clippy_lint! { /// **What it does:** Checks for usage of `as` conversions. /// + /// Note that this lint is specialized in linting *every single* use of `as` + /// regardless of whether good alternatives exist or not. + /// If you want more precise lints for `as`, please consider using these separate lints: + /// `unnecessary_cast`, `cast_lossless/possible_truncation/possible_wrap/precision_loss/sign_loss`, + /// `fn_to_numeric_cast(_with_truncation)`, `char_lit_as_u8`, `ref_to_mut` and `ptr_as_ptr`. + /// There is a good explanation the reason why this lint should work in this way and how it is useful + /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). + /// /// **Why is this bad?** `as` conversions will perform many kinds of /// conversions, including silently lossy conversions and dangerous coercions. /// There are cases when it makes sense to use `as`, so the lint is diff --git a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs index f136aa4572a..ae64c688744 100644 --- a/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs +++ b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs @@ -18,7 +18,7 @@ declare_clippy_lint! { /// other solution is to ensure the mutex is unlocked before calling await, /// either by introducing a scope or an explicit call to Drop::drop. /// - /// **Known problems:** None. + /// **Known problems:** Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). /// /// **Example:** /// @@ -57,7 +57,7 @@ declare_clippy_lint! { /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point /// risks panics from a mutable ref shared while other refs are outstanding. /// - /// **Known problems:** None. + /// **Known problems:** Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). /// /// **Example:** /// diff --git a/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs new file mode 100644 index 00000000000..6969ac949d8 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -0,0 +1,86 @@ +use crate::utils::paths::STRING; +use crate::utils::{match_def_path, span_lint_and_help}; +use if_chain::if_chain; +use rustc_ast::ast::LitKind; +use rustc_hir::{Expr, ExprKind, PathSegment}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{source_map::Spanned, Span}; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for calls to `ends_with` with possible file extensions + /// and suggests to use a case-insensitive approach instead. + /// + /// **Why is this bad?** + /// `ends_with` is case-sensitive and may not detect files with a valid extension. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.ends_with(".rs") + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn is_rust_file(filename: &str) -> bool { + /// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true) + /// } + /// ``` + pub CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + pedantic, + "Checks for calls to ends_with with case-sensitive file extensions" +} + +declare_lint_pass!(CaseSensitiveFileExtensionComparisons => [CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS]); + +fn check_case_sensitive_file_extension_comparison(ctx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Span> { + if_chain! { + if let ExprKind::MethodCall(PathSegment { ident, .. }, _, [obj, extension, ..], span) = expr.kind; + if ident.as_str() == "ends_with"; + if let ExprKind::Lit(Spanned { node: LitKind::Str(ext_literal, ..), ..}) = extension.kind; + if (2..=6).contains(&ext_literal.as_str().len()); + if ext_literal.as_str().starts_with('.'); + if ext_literal.as_str().chars().skip(1).all(|c| c.is_uppercase() || c.is_digit(10)) + || ext_literal.as_str().chars().skip(1).all(|c| c.is_lowercase() || c.is_digit(10)); + then { + let mut ty = ctx.typeck_results().expr_ty(obj); + ty = match ty.kind() { + ty::Ref(_, ty, ..) => ty, + _ => ty + }; + + match ty.kind() { + ty::Str => { + return Some(span); + }, + ty::Adt(&ty::AdtDef { did, .. }, _) => { + if match_def_path(ctx, did, &STRING) { + return Some(span); + } + }, + _ => { return None; } + } + } + } + None +} + +impl LateLintPass<'tcx> for CaseSensitiveFileExtensionComparisons { + fn check_expr(&mut self, ctx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let Some(span) = check_case_sensitive_file_extension_comparison(ctx, expr) { + span_lint_and_help( + ctx, + CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, + span, + "case-sensitive file extension comparison", + None, + "consider using a case-insensitive comparison instead", + ); + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index b3ebdf4ca30..f21a734bb43 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -57,9 +57,9 @@ impl CognitiveComplexity { let expr = &body.value; - let mut helper = CCHelper { cc: 1, returns: 0 }; + let mut helper = CcHelper { cc: 1, returns: 0 }; helper.visit_expr(expr); - let CCHelper { cc, returns } = helper; + let CcHelper { cc, returns } = helper; let ret_ty = cx.typeck_results().node_type(expr.hir_id); let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::result_type) { returns @@ -136,12 +136,12 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { } } -struct CCHelper { +struct CcHelper { cc: u64, returns: u64, } -impl<'tcx> Visitor<'tcx> for CCHelper { +impl<'tcx> Visitor<'tcx> for CcHelper { type Map = Map<'tcx>; fn visit_expr(&mut self, e: &'tcx Expr<'_>) { diff --git a/src/tools/clippy/clippy_lints/src/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/collapsible_match.rs index a34ba2d00a8..604ba102046 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_match.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_match.rs @@ -2,7 +2,7 @@ use crate::utils::visitors::LocalUsedVisitor; use crate::utils::{span_lint_and_then, SpanlessEq}; use if_chain::if_chain; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind}; +use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{DefIdTree, TyCtxt}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -72,8 +72,7 @@ fn check_arm(arm: &Arm<'_>, wild_outer_arm: &Arm<'_>, cx: &LateContext<'_>) { if arms_inner.iter().all(|arm| arm.guard.is_none()); // match expression must be a local binding // match <local> { .. } - if let ExprKind::Path(QPath::Resolved(None, path)) = expr_in.kind; - if let Res::Local(binding_id) = path.res; + if let Some(binding_id) = addr_adjusted_binding(expr_in, cx); // one of the branches must be "wild-like" if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(arm_inner, cx.tcx)); let (wild_inner_arm, non_wild_inner_arm) = @@ -85,7 +84,12 @@ fn check_arm(arm: &Arm<'_>, wild_outer_arm: &Arm<'_>, cx: &LateContext<'_>) { // the "wild-like" branches must be equal if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body); // the binding must not be used in the if guard - if !matches!(arm.guard, Some(Guard::If(guard)) if LocalUsedVisitor::new(binding_id).check_expr(guard)); + if match arm.guard { + None => true, + Some(Guard::If(expr) | Guard::IfLet(_, expr)) => { + !LocalUsedVisitor::new(binding_id).check_expr(expr) + } + }; // ...or anywhere in the inner match if !arms_inner.iter().any(|arm| LocalUsedVisitor::new(binding_id).check_arm(arm)); then { @@ -170,3 +174,20 @@ fn is_none_ctor(res: Res, tcx: TyCtxt<'_>) -> bool { } false } + +/// Retrieves a binding ID with optional `&` and/or `*` operators removed. (e.g. `&**foo`) +/// Returns `None` if a non-reference type is de-referenced. +/// For example, if `Vec` is de-referenced to a slice, `None` is returned. +fn addr_adjusted_binding(mut expr: &Expr<'_>, cx: &LateContext<'_>) -> Option<HirId> { + loop { + match expr.kind { + ExprKind::AddrOf(_, _, e) => expr = e, + ExprKind::Path(QPath::Resolved(None, path)) => match path.res { + Res::Local(binding_id) => break Some(binding_id), + _ => break None, + }, + ExprKind::Unary(UnOp::UnDeref, e) if cx.typeck_results().expr_ty(e).is_ref() => expr = e, + _ => break None, + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs index 47b3cc3ad30..89088c533ed 100644 --- a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs +++ b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs @@ -179,3 +179,12 @@ declare_deprecated_lint! { pub UNKNOWN_CLIPPY_LINTS, "this lint has been integrated into the `unknown_lints` rustc lint" } + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been replaced by `manual_find_map`, a + /// more specific lint. + pub FIND_MAP, + "this lint has been replaced by `manual_find_map`, a more specific lint" +} diff --git a/src/tools/clippy/clippy_lints/src/enum_clike.rs b/src/tools/clippy/clippy_lints/src/enum_clike.rs index ae56a8ba5ab..aa235642ac3 100644 --- a/src/tools/clippy/clippy_lints/src/enum_clike.rs +++ b/src/tools/clippy/clippy_lints/src/enum_clike.rs @@ -5,8 +5,8 @@ use crate::consts::{miri_to_const, Constant}; use crate::utils::span_lint; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, IntTy, UintTy}; use rustc_middle::ty::util::IntTypeExt; +use rustc_middle::ty::{self, IntTy, UintTy}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use std::convert::TryFrom; diff --git a/src/tools/clippy/clippy_lints/src/exhaustive_items.rs b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs new file mode 100644 index 00000000000..32b1299efce --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/exhaustive_items.rs @@ -0,0 +1,102 @@ +use crate::utils::{indent_of, span_lint_and_then}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// **What it does:** Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` + /// + /// **Why is this bad?** Exhaustive enums are typically fine, but a project which does + /// not wish to make a stability commitment around exported enums may wish to + /// disable them by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// enum Foo { + /// Bar, + /// Baz + /// } + /// ``` + pub EXHAUSTIVE_ENUMS, + restriction, + "detects exported enums that have not been marked #[non_exhaustive]" +} + +declare_clippy_lint! { + /// **What it does:** Warns on any exported `structs`s that are not tagged `#[non_exhaustive]` + /// + /// **Why is this bad?** Exhaustive structs are typically fine, but a project which does + /// not wish to make a stability commitment around exported structs may wish to + /// disable them by default. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + /// Use instead: + /// ```rust + /// #[non_exhaustive] + /// struct Foo { + /// bar: u8, + /// baz: String, + /// } + /// ``` + pub EXHAUSTIVE_STRUCTS, + restriction, + "detects exported structs that have not been marked #[non_exhaustive]" +} + +declare_lint_pass!(ExhaustiveItems => [EXHAUSTIVE_ENUMS, EXHAUSTIVE_STRUCTS]); + +impl LateLintPass<'_> for ExhaustiveItems { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + if_chain! { + if let ItemKind::Enum(..) | ItemKind::Struct(..) = item.kind; + if cx.access_levels.is_exported(item.hir_id); + if !item.attrs.iter().any(|a| a.has_name(sym::non_exhaustive)); + then { + let (lint, msg) = if let ItemKind::Enum(..) = item.kind { + (EXHAUSTIVE_ENUMS, "exported enums should not be exhaustive") + } else { + (EXHAUSTIVE_STRUCTS, "exported structs should not be exhaustive") + }; + let suggestion_span = item.span.shrink_to_lo(); + let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0)); + span_lint_and_then( + cx, + lint, + item.span, + msg, + |diag| { + let sugg = format!("#[non_exhaustive]\n{}", indent); + diag.span_suggestion(suggestion_span, + "try adding #[non_exhaustive]", + sugg, + Applicability::MaybeIncorrect); + } + ); + + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/future_not_send.rs b/src/tools/clippy/clippy_lints/src/future_not_send.rs index a3a38fad9a3..7208e66ff7b 100644 --- a/src/tools/clippy/clippy_lints/src/future_not_send.rs +++ b/src/tools/clippy/clippy_lints/src/future_not_send.rs @@ -92,13 +92,8 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { |db| { cx.tcx.infer_ctxt().enter(|infcx| { for FulfillmentError { obligation, .. } in send_errors { - infcx.maybe_note_obligation_cause_for_async_await( - db, - &obligation, - ); - if let Trait(trait_pred, _) = - obligation.predicate.kind().skip_binder() - { + infcx.maybe_note_obligation_cause_for_async_await(db, &obligation); + if let Trait(trait_pred, _) = obligation.predicate.kind().skip_binder() { db.note(&format!( "`{}` doesn't implement `{}`", trait_pred.self_ty(), diff --git a/src/tools/clippy/clippy_lints/src/int_plus_one.rs b/src/tools/clippy/clippy_lints/src/int_plus_one.rs index c629ee05ab9..260b8988d37 100644 --- a/src/tools/clippy/clippy_lints/src/int_plus_one.rs +++ b/src/tools/clippy/clippy_lints/src/int_plus_one.rs @@ -46,8 +46,8 @@ declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]); #[derive(Copy, Clone)] enum Side { - LHS, - RHS, + Lhs, + Rhs, } impl IntPlusOne { @@ -66,11 +66,11 @@ impl IntPlusOne { match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) { // `-1 + x` (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { - Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::LHS) + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) }, // `x - 1` (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::LHS) + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) }, _ => None, } @@ -82,10 +82,10 @@ impl IntPlusOne { match (&rhslhs.kind, &rhsrhs.kind) { // `y + 1` and `1 + y` (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::RHS) + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) }, (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::RHS) + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) }, _ => None, } @@ -97,10 +97,10 @@ impl IntPlusOne { match (&lhslhs.kind, &lhsrhs.kind) { // `1 + x` and `x + 1` (&ExprKind::Lit(ref lit), _) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::LHS) + Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs) }, (_, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::LHS) + Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs) }, _ => None, } @@ -110,11 +110,11 @@ impl IntPlusOne { match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) { // `-1 + y` (BinOpKind::Add, &ExprKind::Lit(ref lit), _) if Self::check_lit(lit, -1) => { - Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::RHS) + Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs) }, // `y - 1` (BinOpKind::Sub, _, &ExprKind::Lit(ref lit)) if Self::check_lit(lit, 1) => { - Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::RHS) + Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs) }, _ => None, } @@ -138,8 +138,8 @@ impl IntPlusOne { if let Some(snippet) = snippet_opt(cx, node.span) { if let Some(other_side_snippet) = snippet_opt(cx, other_side.span) { let rec = match side { - Side::LHS => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)), - Side::RHS => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)), + Side::Lhs => Some(format!("{} {} {}", snippet, binop_string, other_side_snippet)), + Side::Rhs => Some(format!("{} {} {}", other_side_snippet, binop_string, snippet)), }; return rec; } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index 781282cdfbd..54007c29c6c 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -170,6 +170,7 @@ mod blocks_in_if_conditions; mod booleans; mod bytecount; mod cargo_common_metadata; +mod case_sensitive_file_extension_comparisons; mod checked_conversions; mod cognitive_complexity; mod collapsible_if; @@ -199,6 +200,7 @@ mod escape; mod eta_reduction; mod eval_order_dependence; mod excessive_bools; +mod exhaustive_items; mod exit; mod explicit_write; mod fallible_impl_from; @@ -300,6 +302,7 @@ mod redundant_closure_call; mod redundant_else; mod redundant_field_names; mod redundant_pub_crate; +mod redundant_slicing; mod redundant_static_lifetimes; mod ref_option_ref; mod reference; @@ -339,6 +342,7 @@ mod unused_self; mod unused_unit; mod unwrap; mod unwrap_in_result; +mod upper_case_acronyms; mod use_self; mod useless_conversion; mod vec; @@ -506,6 +510,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: "clippy::unknown_clippy_lints", "this lint has been integrated into the `unknown_lints` rustc lint", ); + store.register_removed( + "clippy::find_map", + "this lint has been replaced by `manual_find_map`, a more specific lint", + ); // end deprecated lints, do not remove this comment, it’s used in `update_lints` // begin register lints, do not remove this comment, it’s used in `update_lints` @@ -561,6 +569,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &booleans::NONMINIMAL_BOOL, &bytecount::NAIVE_BYTECOUNT, &cargo_common_metadata::CARGO_COMMON_METADATA, + &case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS, &checked_conversions::CHECKED_CONVERSIONS, &cognitive_complexity::COGNITIVE_COMPLEXITY, &collapsible_if::COLLAPSIBLE_ELSE_IF, @@ -610,6 +619,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &eval_order_dependence::EVAL_ORDER_DEPENDENCE, &excessive_bools::FN_PARAMS_EXCESSIVE_BOOLS, &excessive_bools::STRUCT_EXCESSIVE_BOOLS, + &exhaustive_items::EXHAUSTIVE_ENUMS, + &exhaustive_items::EXHAUSTIVE_STRUCTS, &exit::EXIT, &explicit_write::EXPLICIT_WRITE, &fallible_impl_from::FALLIBLE_IMPL_FROM, @@ -731,11 +742,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::FILTER_MAP, &methods::FILTER_MAP_NEXT, &methods::FILTER_NEXT, - &methods::FIND_MAP, &methods::FLAT_MAP_IDENTITY, &methods::FROM_ITER_INSTEAD_OF_COLLECT, &methods::GET_UNWRAP, &methods::INEFFICIENT_TO_STRING, + &methods::INSPECT_FOR_EACH, &methods::INTO_ITER_ON_REF, &methods::ITERATOR_STEP_BY_ZERO, &methods::ITER_CLONED_COLLECT, @@ -743,6 +754,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &methods::ITER_NTH, &methods::ITER_NTH_ZERO, &methods::ITER_SKIP_NEXT, + &methods::MANUAL_FILTER_MAP, + &methods::MANUAL_FIND_MAP, &methods::MANUAL_SATURATING_ARITHMETIC, &methods::MAP_COLLECT_RESULT_UNIT, &methods::MAP_FLATTEN, @@ -850,6 +863,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &redundant_else::REDUNDANT_ELSE, &redundant_field_names::REDUNDANT_FIELD_NAMES, &redundant_pub_crate::REDUNDANT_PUB_CRATE, + &redundant_slicing::REDUNDANT_SLICING, &redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES, &ref_option_ref::REF_OPTION_REF, &reference::DEREF_ADDROF, @@ -942,6 +956,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &unwrap::PANICKING_UNWRAP, &unwrap::UNNECESSARY_UNWRAP, &unwrap_in_result::UNWRAP_IN_RESULT, + &upper_case_acronyms::UPPER_CASE_ACRONYMS, &use_self::USE_SELF, &useless_conversion::USELESS_CONVERSION, &vec::USELESS_VEC, @@ -981,7 +996,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: } store.register_late_pass(|| box utils::author::Author); store.register_late_pass(|| box await_holding_invalid::AwaitHolding); - store.register_late_pass(|| box serde_api::SerdeAPI); + store.register_late_pass(|| box serde_api::SerdeApi); let vec_box_size_threshold = conf.vec_box_size_threshold; store.register_late_pass(move || box types::Types::new(vec_box_size_threshold)); store.register_late_pass(|| box booleans::NonminimalBool); @@ -1092,6 +1107,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box eval_order_dependence::EvalOrderDependence); store.register_late_pass(|| box missing_doc::MissingDoc::new()); store.register_late_pass(|| box missing_inline::MissingInline); + store.register_late_pass(move || box exhaustive_items::ExhaustiveItems); store.register_late_pass(|| box if_let_some_result::OkIfLet); store.register_late_pass(|| box partialeq_ne_impl::PartialEqNeImpl); store.register_late_pass(|| box unused_io_amount::UnusedIoAmount); @@ -1172,6 +1188,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let enum_variant_name_threshold = conf.enum_variant_name_threshold; store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold)); store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments); + store.register_early_pass(|| box upper_case_acronyms::UpperCaseAcronyms); store.register_late_pass(|| box default::Default::default()); store.register_late_pass(|| box unused_self::UnusedSelf); store.register_late_pass(|| box mutable_debug_assertion::DebugAssertWithMutCall); @@ -1229,6 +1246,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box zero_sized_map_values::ZeroSizedMapValues); store.register_late_pass(|| box vec_init_then_push::VecInitThenPush::default()); store.register_late_pass(move || box types::PtrAsPtr::new(msrv)); + store.register_late_pass(|| box case_sensitive_file_extension_comparisons::CaseSensitiveFileExtensionComparisons); + store.register_late_pass(|| box redundant_slicing::RedundantSlicing); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1239,6 +1258,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&create_dir::CREATE_DIR), LintId::of(&dbg_macro::DBG_MACRO), LintId::of(&else_if_without_else::ELSE_IF_WITHOUT_ELSE), + LintId::of(&exhaustive_items::EXHAUSTIVE_ENUMS), + LintId::of(&exhaustive_items::EXHAUSTIVE_STRUCTS), LintId::of(&exit::EXIT), LintId::of(&float_literal::LOSSY_FLOAT_LITERAL), LintId::of(&implicit_return::IMPLICIT_RETURN), @@ -1286,6 +1307,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&await_holding_invalid::AWAIT_HOLDING_LOCK), LintId::of(&await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(&bit_mask::VERBOSE_BIT_MASK), + LintId::of(&case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS), LintId::of(&checked_conversions::CHECKED_CONVERSIONS), LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), LintId::of(©_iterator::COPY_ITERATOR), @@ -1323,7 +1345,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&matches::SINGLE_MATCH_ELSE), LintId::of(&methods::FILTER_MAP), LintId::of(&methods::FILTER_MAP_NEXT), - LintId::of(&methods::FIND_MAP), LintId::of(&methods::INEFFICIENT_TO_STRING), LintId::of(&methods::MAP_FLATTEN), LintId::of(&methods::MAP_UNWRAP_OR), @@ -1509,6 +1530,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::FILTER_NEXT), LintId::of(&methods::FLAT_MAP_IDENTITY), LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT), + LintId::of(&methods::INSPECT_FOR_EACH), LintId::of(&methods::INTO_ITER_ON_REF), LintId::of(&methods::ITERATOR_STEP_BY_ZERO), LintId::of(&methods::ITER_CLONED_COLLECT), @@ -1516,6 +1538,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::ITER_NTH), LintId::of(&methods::ITER_NTH_ZERO), LintId::of(&methods::ITER_SKIP_NEXT), + LintId::of(&methods::MANUAL_FILTER_MAP), + LintId::of(&methods::MANUAL_FIND_MAP), LintId::of(&methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(&methods::MAP_COLLECT_RESULT_UNIT), LintId::of(&methods::NEW_RET_NO_SELF), @@ -1589,6 +1613,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&redundant_clone::REDUNDANT_CLONE), LintId::of(&redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), + LintId::of(&redundant_slicing::REDUNDANT_SLICING), LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), @@ -1651,6 +1676,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&unused_unit::UNUSED_UNIT), LintId::of(&unwrap::PANICKING_UNWRAP), LintId::of(&unwrap::UNNECESSARY_UNWRAP), + LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS), LintId::of(&useless_conversion::USELESS_CONVERSION), LintId::of(&vec::USELESS_VEC), LintId::of(&vec_init_then_push::VEC_INIT_THEN_PUSH), @@ -1767,6 +1793,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::FN_TO_NUMERIC_CAST_WITH_TRUNCATION), LintId::of(&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME), LintId::of(&unused_unit::UNUSED_UNIT), + LintId::of(&upper_case_acronyms::UPPER_CASE_ACRONYMS), LintId::of(&write::PRINTLN_EMPTY_STRING), LintId::of(&write::PRINT_LITERAL), LintId::of(&write::PRINT_WITH_NEWLINE), @@ -1808,6 +1835,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&methods::CLONE_ON_COPY), LintId::of(&methods::FILTER_NEXT), LintId::of(&methods::FLAT_MAP_IDENTITY), + LintId::of(&methods::INSPECT_FOR_EACH), + LintId::of(&methods::MANUAL_FILTER_MAP), + LintId::of(&methods::MANUAL_FIND_MAP), LintId::of(&methods::OPTION_AS_REF_DEREF), LintId::of(&methods::SEARCH_IS_SOME), LintId::of(&methods::SKIP_WHILE_NEXT), @@ -1832,6 +1862,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&redundant_closure_call::REDUNDANT_CLOSURE_CALL), + LintId::of(&redundant_slicing::REDUNDANT_SLICING), LintId::of(&reference::DEREF_ADDROF), LintId::of(&reference::REF_IN_DEREF), LintId::of(&repeat_once::REPEAT_ONCE), diff --git a/src/tools/clippy/clippy_lints/src/loops.rs b/src/tools/clippy/clippy_lints/src/loops.rs index f89075005bc..5211ca7da32 100644 --- a/src/tools/clippy/clippy_lints/src/loops.rs +++ b/src/tools/clippy/clippy_lints/src/loops.rs @@ -1069,7 +1069,6 @@ fn get_assignments<'a: 'c, 'tcx: 'c, 'c>( ) -> impl Iterator<Item = Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>> + 'c { // As the `filter` and `map` below do different things, I think putting together // just increases complexity. (cc #3188 and #4193) - #[allow(clippy::filter_map)] stmts .iter() .filter_map(move |stmt| match stmt.kind { diff --git a/src/tools/clippy/clippy_lints/src/main_recursion.rs b/src/tools/clippy/clippy_lints/src/main_recursion.rs index eceae706e4f..1ed3f3de839 100644 --- a/src/tools/clippy/clippy_lints/src/main_recursion.rs +++ b/src/tools/clippy/clippy_lints/src/main_recursion.rs @@ -43,8 +43,7 @@ impl LateLintPass<'_> for MainRecursion { if_chain! { if let ExprKind::Call(func, _) = &expr.kind; - if let ExprKind::Path(path) = &func.kind; - if let QPath::Resolved(_, path) = &path; + if let ExprKind::Path(QPath::Resolved(_, path)) = &func.kind; if let Some(def_id) = path.res.opt_def_id(); if is_entrypoint_fn(cx, def_id); then { diff --git a/src/tools/clippy/clippy_lints/src/manual_ok_or.rs b/src/tools/clippy/clippy_lints/src/manual_ok_or.rs index b97d97ea1a5..8c77e155b70 100644 --- a/src/tools/clippy/clippy_lints/src/manual_ok_or.rs +++ b/src/tools/clippy/clippy_lints/src/manual_ok_or.rs @@ -23,9 +23,6 @@ declare_clippy_lint! { /// ```rust /// let foo: Option<i32> = None; /// foo.map_or(Err("error"), |v| Ok(v)); - /// - /// let foo: Option<i32> = None; - /// foo.map_or(Err("error"), |v| Ok(v)); /// ``` /// /// Use instead: diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs index e50d11a4d71..01126e86199 100644 --- a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -131,7 +131,7 @@ fn reduce_unit_expression<'a>(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Some(expr.span) }, hir::ExprKind::Block(ref block, _) => { - match (&block.stmts[..], block.expr.as_ref()) { + match (block.stmts, block.expr.as_ref()) { (&[], Some(inner_expr)) => { // If block only contains an expression, // reduce `{ X }` to `X` diff --git a/src/tools/clippy/clippy_lints/src/matches.rs b/src/tools/clippy/clippy_lints/src/matches.rs index 04b35835c6b..5f62d2d1316 100644 --- a/src/tools/clippy/clippy_lints/src/matches.rs +++ b/src/tools/clippy/clippy_lints/src/matches.rs @@ -2,10 +2,10 @@ use crate::consts::{constant, miri_to_const, Constant}; use crate::utils::sugg::Sugg; use crate::utils::usage::is_unused; use crate::utils::{ - expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable, - is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg, remove_blocks, - snippet, snippet_block, snippet_opt, snippet_with_applicability, span_lint_and_help, span_lint_and_note, - span_lint_and_sugg, span_lint_and_then, + expr_block, get_arg_name, get_parent_expr, implements_trait, in_macro, indent_of, is_allowed, is_expn_of, + is_refutable, is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg, + peel_hir_pat_refs, peel_mid_ty_refs, peel_n_hir_expr_refs, remove_blocks, snippet, snippet_block, snippet_opt, + snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then, }; use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash}; use if_chain::if_chain; @@ -728,20 +728,60 @@ fn report_single_match_single_pattern( let els_str = els.map_or(String::new(), |els| { format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span))) }); + + let (msg, sugg) = if_chain! { + let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat); + if let PatKind::Path(_) | PatKind::Lit(_) = pat.kind; + let (ty, ty_ref_count) = peel_mid_ty_refs(cx.typeck_results().expr_ty(ex)); + if let Some(trait_id) = cx.tcx.lang_items().structural_peq_trait(); + if ty.is_integral() || ty.is_char() || ty.is_str() || implements_trait(cx, ty, trait_id, &[]); + then { + // scrutinee derives PartialEq and the pattern is a constant. + let pat_ref_count = match pat.kind { + // string literals are already a reference. + PatKind::Lit(Expr { kind: ExprKind::Lit(lit), .. }) if lit.node.is_str() => pat_ref_count + 1, + _ => pat_ref_count, + }; + // References are only implicitly added to the pattern, so no overflow here. + // e.g. will work: match &Some(_) { Some(_) => () } + // will not: match Some(_) { &Some(_) => () } + let ref_count_diff = ty_ref_count - pat_ref_count; + + // Try to remove address of expressions first. + let (ex, removed) = peel_n_hir_expr_refs(ex, ref_count_diff); + let ref_count_diff = ref_count_diff - removed; + + let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; + let sugg = format!( + "if {} == {}{} {}{}", + snippet(cx, ex.span, ".."), + // PartialEq for different reference counts may not exist. + "&".repeat(ref_count_diff), + snippet(cx, arms[0].pat.span, ".."), + expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } else { + let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; + let sugg = format!( + "if let {} = {} {}{}", + snippet(cx, arms[0].pat.span, ".."), + snippet(cx, ex.span, ".."), + expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), + els_str, + ); + (msg, sugg) + } + }; + span_lint_and_sugg( cx, lint, expr.span, - "you seem to be trying to use match for destructuring a single pattern. Consider using `if \ - let`", + msg, "try this", - format!( - "if let {} = {} {}{}", - snippet(cx, arms[0].pat.span, ".."), - snippet(cx, ex.span, ".."), - expr_block(cx, &arms[0].body, None, "..", Some(expr.span)), - els_str, - ), + sugg, Applicability::HasPlaceholders, ); } @@ -1185,6 +1225,14 @@ fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr } else { pat }; + + // strip potential borrows (#6503), but only if the type is a reference + let mut ex_new = ex; + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { + if let ty::Ref(..) = cx.typeck_results().expr_ty(&ex_inner).kind() { + ex_new = ex_inner; + } + }; span_lint_and_sugg( cx, MATCH_LIKE_MATCHES_MACRO, @@ -1194,7 +1242,7 @@ fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr format!( "{}matches!({}, {})", if b0 { "" } else { "!" }, - snippet_with_applicability(cx, ex.span, "..", &mut applicability), + snippet_with_applicability(cx, ex_new.span, "..", &mut applicability), pat_and_guard, ), applicability, diff --git a/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs new file mode 100644 index 00000000000..6d41ee38a27 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/methods/inspect_for_each.rs @@ -0,0 +1,23 @@ +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +use crate::utils::{match_trait_method, paths, span_lint_and_help}; + +use super::INSPECT_FOR_EACH; + +/// lint use of `inspect().for_each()` for `Iterators` +pub(super) fn lint<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, inspect_span: Span) { + if match_trait_method(cx, expr, &paths::ITERATOR) { + let msg = "called `inspect(..).for_each(..)` on an `Iterator`"; + let hint = "move the code from `inspect(..)` to `for_each(..)` and remove the `inspect(..)`"; + span_lint_and_help( + cx, + INSPECT_FOR_EACH, + inspect_span.with_hi(expr.span.hi()), + msg, + None, + hint, + ); + } +} diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs index 7adf6bdeac5..a17c5996293 100644 --- a/src/tools/clippy/clippy_lints/src/methods/mod.rs +++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs @@ -1,5 +1,6 @@ mod bind_instead_of_map; mod inefficient_to_string; +mod inspect_for_each; mod manual_saturating_arithmetic; mod option_map_unwrap_or; mod unnecessary_filter_map; @@ -14,7 +15,8 @@ use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::{TraitItem, TraitItemKind}; +use rustc_hir::def::Res; +use rustc_hir::{Expr, ExprKind, PatKind, QPath, TraitItem, TraitItemKind, UnOp}; use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::{self, TraitRef, Ty, TyS}; @@ -450,6 +452,58 @@ declare_clippy_lint! { } declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.filter(_).map(_)` that can be written more simply + /// as `filter_map(_)`. + /// + /// **Why is this bad?** Redundant code in the `filter` and `map` operations is poor style and + /// less performant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// (0_i32..10) + /// .filter(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Good: + /// ```rust + /// (0_i32..10).filter_map(|n| n.checked_add(1)); + /// ``` + pub MANUAL_FILTER_MAP, + complexity, + "using `_.filter(_).map(_)` in a way that can be written more simply as `filter_map(_)`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for usage of `_.find(_).map(_)` that can be written more simply + /// as `find_map(_)`. + /// + /// **Why is this bad?** Redundant code in the `find` and `map` operations is poor style and + /// less performant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// Bad: + /// ```rust + /// (0_i32..10) + /// .find(|n| n.checked_add(1).is_some()) + /// .map(|n| n.checked_add(1).unwrap()); + /// ``` + /// + /// Good: + /// ```rust + /// (0_i32..10).find_map(|n| n.checked_add(1)); + /// ``` + pub MANUAL_FIND_MAP, + complexity, + "using `_.find(_).map(_)` in a way that can be written more simply as `find_map(_)`" +} + +declare_clippy_lint! { /// **What it does:** Checks for usage of `_.filter_map(_).next()`. /// /// **Why is this bad?** Readability, this can be written more concisely as @@ -494,28 +548,6 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for usage of `_.find(_).map(_)`. - /// - /// **Why is this bad?** Readability, this can be written more concisely as - /// `_.find_map(_)`. - /// - /// **Known problems:** Often requires a condition + Option/Iterator creation - /// inside the closure. - /// - /// **Example:** - /// ```rust - /// (0..3).find(|x| *x == 2).map(|x| x * 2); - /// ``` - /// Can be written as - /// ```rust - /// (0..3).find_map(|x| if x == 2 { Some(x * 2) } else { None }); - /// ``` - pub FIND_MAP, - pedantic, - "using a combination of `find` and `map` can usually be written as a single method call" -} - -declare_clippy_lint! { /// **What it does:** Checks for an iterator or string search (such as `find()`, /// `position()`, or `rposition()`) followed by a call to `is_some()`. /// @@ -1405,6 +1437,36 @@ declare_clippy_lint! { "use `.collect()` instead of `::from_iter()`" } +declare_clippy_lint! { + /// **What it does:** Checks for usage of `inspect().for_each()`. + /// + /// **Why is this bad?** It is the same as performing the computation + /// inside `inspect` at the beginning of the closure in `for_each`. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// [1,2,3,4,5].iter() + /// .inspect(|&x| println!("inspect the number: {}", x)) + /// .for_each(|&x| { + /// assert!(x >= 0); + /// }); + /// ``` + /// Can be written as + /// ```rust + /// [1,2,3,4,5].iter() + /// .for_each(|&x| { + /// println!("inspect the number: {}", x); + /// assert!(x >= 0); + /// }); + /// ``` + pub INSPECT_FOR_EACH, + complexity, + "using `.inspect().for_each()`, which can be replaced with `.for_each()`" +} + pub struct Methods { msrv: Option<RustcVersion>, } @@ -1442,9 +1504,10 @@ impl_lint_pass!(Methods => [ FILTER_NEXT, SKIP_WHILE_NEXT, FILTER_MAP, + MANUAL_FILTER_MAP, + MANUAL_FIND_MAP, FILTER_MAP_NEXT, FLAT_MAP_IDENTITY, - FIND_MAP, MAP_FLATTEN, ITERATOR_STEP_BY_ZERO, ITER_NEXT_SLICE, @@ -1467,6 +1530,7 @@ impl_lint_pass!(Methods => [ UNNECESSARY_LAZY_EVALUATIONS, MAP_COLLECT_RESULT_UNIT, FROM_ITER_INSTEAD_OF_COLLECT, + INSPECT_FOR_EACH, ]); impl<'tcx> LateLintPass<'tcx> for Methods { @@ -1508,10 +1572,10 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ["next", "filter"] => lint_filter_next(cx, expr, arg_lists[1]), ["next", "skip_while"] => lint_skip_while_next(cx, expr, arg_lists[1]), ["next", "iter"] => lint_iter_next(cx, expr, arg_lists[1]), - ["map", "filter"] => lint_filter_map(cx, expr, arg_lists[1], arg_lists[0]), + ["map", "filter"] => lint_filter_map(cx, expr, false), ["map", "filter_map"] => lint_filter_map_map(cx, expr, arg_lists[1], arg_lists[0]), ["next", "filter_map"] => lint_filter_map_next(cx, expr, arg_lists[1], self.msrv.as_ref()), - ["map", "find"] => lint_find_map(cx, expr, arg_lists[1], arg_lists[0]), + ["map", "find"] => lint_filter_map(cx, expr, true), ["flat_map", "filter"] => lint_filter_flat_map(cx, expr, arg_lists[1], arg_lists[0]), ["flat_map", "filter_map"] => lint_filter_map_flat_map(cx, expr, arg_lists[1], arg_lists[0]), ["flat_map", ..] => lint_flat_map_identity(cx, expr, arg_lists[0], method_spans[0]), @@ -1553,6 +1617,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { ["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"), ["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"), ["collect", "map"] => lint_map_collect(cx, expr, arg_lists[1], arg_lists[0]), + ["for_each", "inspect"] => inspect_for_each::lint(cx, expr, method_spans[1]), _ => {}, } @@ -2955,18 +3020,79 @@ fn lint_skip_while_next<'tcx>( } } -/// lint use of `filter().map()` for `Iterators` -fn lint_filter_map<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _filter_args: &'tcx [hir::Expr<'_>], - _map_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.filter().map()` is an Iterator - if match_trait_method(cx, expr, &paths::ITERATOR) { - let msg = "called `filter(..).map(..)` on an `Iterator`"; - let hint = "this is more succinctly expressed by calling `.filter_map(..)` instead"; - span_lint_and_help(cx, FILTER_MAP, expr.span, msg, None, hint); +/// lint use of `filter().map()` or `find().map()` for `Iterators` +fn lint_filter_map<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, is_find: bool) { + if_chain! { + if let ExprKind::MethodCall(_, _, [map_recv, map_arg], map_span) = expr.kind; + if let ExprKind::MethodCall(_, _, [_, filter_arg], filter_span) = map_recv.kind; + if match_trait_method(cx, map_recv, &paths::ITERATOR); + + // filter(|x| ...is_some())... + if let ExprKind::Closure(_, _, filter_body_id, ..) = filter_arg.kind; + let filter_body = cx.tcx.hir().body(filter_body_id); + if let [filter_param] = filter_body.params; + // optional ref pattern: `filter(|&x| ..)` + let (filter_pat, is_filter_param_ref) = if let PatKind::Ref(ref_pat, _) = filter_param.pat.kind { + (ref_pat, true) + } else { + (filter_param.pat, false) + }; + // closure ends with is_some() or is_ok() + if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; + if let ExprKind::MethodCall(path, _, [filter_arg], _) = filter_body.value.kind; + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); + if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::option_type, opt_ty.did) { + Some(false) + } else if cx.tcx.is_diagnostic_item(sym::result_type, opt_ty.did) { + Some(true) + } else { + None + }; + if path.ident.name.as_str() == if is_result { "is_ok" } else { "is_some" }; + + // ...map(|x| ...unwrap()) + if let ExprKind::Closure(_, _, map_body_id, ..) = map_arg.kind; + let map_body = cx.tcx.hir().body(map_body_id); + if let [map_param] = map_body.params; + if let PatKind::Binding(_, map_param_id, map_param_ident, None) = map_param.pat.kind; + // closure ends with expect() or unwrap() + if let ExprKind::MethodCall(seg, _, [map_arg, ..], _) = map_body.value.kind; + if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); + + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { + // in `filter(|x| ..)`, replace `*x` with `x` + let a_path = if_chain! { + if !is_filter_param_ref; + if let ExprKind::Unary(UnOp::UnDeref, expr_path) = a.kind; + then { expr_path } else { a } + }; + // let the filter closure arg and the map closure arg be equal + if_chain! { + if let ExprKind::Path(QPath::Resolved(None, a_path)) = a_path.kind; + if let ExprKind::Path(QPath::Resolved(None, b_path)) = b.kind; + if a_path.res == Res::Local(filter_param_id); + if b_path.res == Res::Local(map_param_id); + if TyS::same_type(cx.typeck_results().expr_ty_adjusted(a), cx.typeck_results().expr_ty_adjusted(b)); + then { + return true; + } + } + false + }; + if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + then { + let span = filter_span.to(map_span); + let (filter_name, lint) = if is_find { + ("find", MANUAL_FIND_MAP) + } else { + ("filter", MANUAL_FILTER_MAP) + }; + let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); + let to_opt = if is_result { ".ok()" } else { "" }; + let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, + snippet(cx, map_arg.span, ".."), to_opt); + span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); + } } } @@ -3004,21 +3130,6 @@ fn lint_filter_map_next<'tcx>( } } -/// lint use of `find().map()` for `Iterators` -fn lint_find_map<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - _find_args: &'tcx [hir::Expr<'_>], - map_args: &'tcx [hir::Expr<'_>], -) { - // lint if caller of `.filter().map()` is an Iterator - if match_trait_method(cx, &map_args[0], &paths::ITERATOR) { - let msg = "called `find(..).map(..)` on an `Iterator`"; - let hint = "this is more succinctly expressed by calling `.find_map(..)` instead"; - span_lint_and_help(cx, FIND_MAP, expr.span, msg, None, hint); - } -} - /// lint use of `filter_map().map()` for `Iterators` fn lint_filter_map_map<'tcx>( cx: &LateContext<'tcx>, @@ -3026,7 +3137,7 @@ fn lint_filter_map_map<'tcx>( _filter_args: &'tcx [hir::Expr<'_>], _map_args: &'tcx [hir::Expr<'_>], ) { - // lint if caller of `.filter().map()` is an Iterator + // lint if caller of `.filter_map().map()` is an Iterator if match_trait_method(cx, expr, &paths::ITERATOR) { let msg = "called `filter_map(..).map(..)` on an `Iterator`"; let hint = "this is more succinctly expressed by only calling `.filter_map(..)` instead"; diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs index 89fe2c19a57..54033f40871 100644 --- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs +++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs @@ -117,9 +117,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { .filter_map(|obligation| { // Note that we do not want to deal with qualified predicates here. match obligation.predicate.kind().no_bound_vars() { - Some(ty::PredicateKind::Trait(pred, _)) if pred.def_id() != sized_trait => { - Some(pred) - }, + Some(ty::PredicateKind::Trait(pred, _)) if pred.def_id() != sized_trait => Some(pred), _ => None, } }) diff --git a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs index 446426b3e61..855529378e6 100644 --- a/src/tools/clippy/clippy_lints/src/non_expressive_names.rs +++ b/src/tools/clippy/clippy_lints/src/non_expressive_names.rs @@ -199,6 +199,10 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { ); return; } + if interned_name.starts_with('_') { + // these bindings are typically unused or represent an ignored portion of a destructuring pattern + return; + } let count = interned_name.chars().count(); if count < 3 { if count == 1 { diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs index c6329a1381c..b5aa3411140 100644 --- a/src/tools/clippy/clippy_lints/src/ptr.rs +++ b/src/tools/clippy/clippy_lints/src/ptr.rs @@ -263,8 +263,7 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: } else if match_type(cx, ty, &paths::COW) { if_chain! { if let TyKind::Rptr(_, MutTy { ref ty, ..} ) = arg.kind; - if let TyKind::Path(ref path) = ty.kind; - if let QPath::Resolved(None, ref pp) = *path; + if let TyKind::Path(QPath::Resolved(None, ref pp)) = ty.kind; if let [ref bx] = *pp.segments; if let Some(ref params) = bx.args; if !params.parenthesized; diff --git a/src/tools/clippy/clippy_lints/src/redundant_slicing.rs b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs new file mode 100644 index 00000000000..e5ced13514f --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_slicing.rs @@ -0,0 +1,67 @@ +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::{lint::in_external_macro, ty::TyS}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +use crate::utils::{is_type_lang_item, snippet_with_applicability, span_lint_and_sugg}; + +declare_clippy_lint! { + /// **What it does:** Checks for redundant slicing expressions which use the full range, and + /// do not change the type. + /// + /// **Why is this bad?** It unnecessarily adds complexity to the expression. + /// + /// **Known problems:** If the type being sliced has an implementation of `Index<RangeFull>` + /// that actually changes anything then it can't be removed. However, this would be surprising + /// to people reading the code and should have a note with it. + /// + /// **Example:** + /// + /// ```ignore + /// fn get_slice(x: &[u32]) -> &[u32] { + /// &x[..] + /// } + /// ``` + /// Use instead: + /// ```ignore + /// fn get_slice(x: &[u32]) -> &[u32] { + /// x + /// } + /// ``` + pub REDUNDANT_SLICING, + complexity, + "redundant slicing of the whole range of a type" +} + +declare_lint_pass!(RedundantSlicing => [REDUNDANT_SLICING]); + +impl LateLintPass<'_> for RedundantSlicing { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + + if_chain! { + if let ExprKind::AddrOf(_, _, addressee) = expr.kind; + if let ExprKind::Index(indexed, range) = addressee.kind; + if is_type_lang_item(cx, cx.typeck_results().expr_ty_adjusted(range), LangItem::RangeFull); + if TyS::same_type(cx.typeck_results().expr_ty(expr), cx.typeck_results().expr_ty(indexed)); + then { + let mut app = Applicability::MachineApplicable; + let hint = snippet_with_applicability(cx, indexed.span, "..", &mut app).into_owned(); + + span_lint_and_sugg( + cx, + REDUNDANT_SLICING, + expr.span, + "redundant slicing of the whole range", + "use the original slice instead", + hint, + app, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/reference.rs b/src/tools/clippy/clippy_lints/src/reference.rs index efe3237990d..e1450466a7c 100644 --- a/src/tools/clippy/clippy_lints/src/reference.rs +++ b/src/tools/clippy/clippy_lints/src/reference.rs @@ -1,3 +1,4 @@ +use crate::utils::sugg::Sugg; use crate::utils::{in_macro, snippet_opt, snippet_with_applicability, span_lint_and_sugg}; use if_chain::if_chain; use rustc_ast::ast::{Expr, ExprKind, Mutability, UnOp}; @@ -110,6 +111,12 @@ declare_clippy_lint! { /// let point = Point(30, 20); /// let x = (&point).0; /// ``` + /// Use instead: + /// ```rust + /// # struct Point(u32, u32); + /// # let point = Point(30, 20); + /// let x = point.0; + /// ``` pub REF_IN_DEREF, complexity, "Use of reference in auto dereference expression." @@ -124,14 +131,19 @@ impl EarlyLintPass for RefInDeref { if let ExprKind::Paren(ref parened) = object.kind; if let ExprKind::AddrOf(_, _, ref inner) = parened.kind; then { - let mut applicability = Applicability::MachineApplicable; + let applicability = if inner.span.from_expansion() { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + let sugg = Sugg::ast(cx, inner, "_").maybe_par(); span_lint_and_sugg( cx, REF_IN_DEREF, object.span, "creating a reference that is immediately dereferenced", "try this", - snippet_with_applicability(cx, inner.span, "_", &mut applicability).to_string(), + sugg.to_string(), applicability, ); } diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs index 63548d8fdb4..e438f92b136 100644 --- a/src/tools/clippy/clippy_lints/src/returns.rs +++ b/src/tools/clippy/clippy_lints/src/returns.rs @@ -131,7 +131,16 @@ impl<'tcx> LateLintPass<'tcx> for Return { _: HirId, ) { match kind { - FnKind::Closure(_) => check_final_expr(cx, &body.value, Some(body.value.span), RetReplacement::Empty), + FnKind::Closure(_) => { + // when returning without value in closure, replace this `return` + // with an empty block to prevent invalid suggestion (see #6501) + let replacement = if let ExprKind::Ret(None) = &body.value.kind { + RetReplacement::Block + } else { + RetReplacement::Empty + }; + check_final_expr(cx, &body.value, Some(body.value.span), replacement) + }, FnKind::ItemFn(..) | FnKind::Method(..) => { if let ExprKind::Block(ref block, _) = body.value.kind { check_block_return(cx, block); diff --git a/src/tools/clippy/clippy_lints/src/serde_api.rs b/src/tools/clippy/clippy_lints/src/serde_api.rs index 44e739725c8..90cf1b6c861 100644 --- a/src/tools/clippy/clippy_lints/src/serde_api.rs +++ b/src/tools/clippy/clippy_lints/src/serde_api.rs @@ -18,9 +18,9 @@ declare_clippy_lint! { "various things that will negatively affect your serde experience" } -declare_lint_pass!(SerdeAPI => [SERDE_API_MISUSE]); +declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]); -impl<'tcx> LateLintPass<'tcx> for SerdeAPI { +impl<'tcx> LateLintPass<'tcx> for SerdeApi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), diff --git a/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs index ea7a76146f5..87e386baadc 100644 --- a/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs +++ b/src/tools/clippy/clippy_lints/src/size_of_in_element_count.rs @@ -35,10 +35,11 @@ declare_clippy_lint! { declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]); -fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Ty<'tcx>> { +fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, inverted: bool) -> Option<Ty<'tcx>> { match expr.kind { ExprKind::Call(count_func, _func_args) => { if_chain! { + if !inverted; if let ExprKind::Path(ref count_func_qpath) = count_func.kind; if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); if match_def_path(cx, def_id, &paths::MEM_SIZE_OF) @@ -50,10 +51,13 @@ fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Ty<'tc } } }, - ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node || BinOpKind::Div == op.node => { - get_size_of_ty(cx, left).or_else(|| get_size_of_ty(cx, right)) + ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node => { + get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, inverted)) }, - ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr), + ExprKind::Binary(op, left, right) if BinOpKind::Div == op.node => { + get_size_of_ty(cx, left, inverted).or_else(|| get_size_of_ty(cx, right, !inverted)) + }, + ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr, inverted), _ => None, } } @@ -128,7 +132,7 @@ impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { // Find a size_of call in the count parameter expression and // check that it's the same type - if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr); + if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr, false); if TyS::same_type(pointee_ty, ty_used_for_size_of); then { span_lint_and_help( diff --git a/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs b/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs index 99e4b293ac6..276a9338819 100644 --- a/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs +++ b/src/tools/clippy/clippy_lints/src/stable_sort_primitive.rs @@ -1,4 +1,4 @@ -use crate::utils::{is_slice_of_primitives, span_lint_and_sugg, sugg::Sugg}; +use crate::utils::{is_slice_of_primitives, span_lint_and_then, sugg::Sugg}; use if_chain::if_chain; @@ -107,25 +107,32 @@ fn detect_stable_sort_primitive(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option impl LateLintPass<'_> for StableSortPrimitive { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let Some(detection) = detect_stable_sort_primitive(cx, expr) { - span_lint_and_sugg( + span_lint_and_then( cx, STABLE_SORT_PRIMITIVE, expr.span, format!( - "used {} instead of {} to sort primitive type `{}`", + "used `{}` on primitive type `{}`", detection.method.stable_name(), - detection.method.unstable_name(), detection.slice_type, ) .as_str(), - "try", - format!( - "{}.{}({})", - detection.slice_name, - detection.method.unstable_name(), - detection.method_args - ), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + expr.span, + "try", + format!( + "{}.{}({})", + detection.slice_name, + detection.method.unstable_name(), + detection.method_args, + ), + Applicability::MachineApplicable, + ); + diag.note( + "an unstable sort would perform faster without any observable difference for this data type", + ); + }, ); } } diff --git a/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs new file mode 100644 index 00000000000..61e7031716a --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/upper_case_acronyms.rs @@ -0,0 +1,93 @@ +use crate::utils::span_lint_and_sugg; +use if_chain::if_chain; +use itertools::Itertools; +use rustc_ast::ast::{Item, ItemKind, Variant}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::symbol::Ident; + +declare_clippy_lint! { + /// **What it does:** Checks for camel case name containing a capitalized acronym. + /// + /// **Why is this bad?** In CamelCase, acronyms count as one word. + /// See [naming conventions](https://rust-lang.github.io/api-guidelines/naming.html#casing-conforms-to-rfc-430-c-case) + /// for more. + /// + /// **Known problems:** When two acronyms are contiguous, the lint can't tell where + /// the first acronym ends and the second starts, so it suggests to lowercase all of + /// the letters in the second acronym. + /// + /// **Example:** + /// + /// ```rust + /// struct HTTPResponse; + /// ``` + /// Use instead: + /// ```rust + /// struct HttpResponse; + /// ``` + pub UPPER_CASE_ACRONYMS, + style, + "capitalized acronyms are against the naming convention" +} + +declare_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); + +fn correct_ident(ident: &str) -> String { + let ident = ident.chars().rev().collect::<String>(); + let fragments = ident + .split_inclusive(|x: char| !x.is_ascii_lowercase()) + .rev() + .map(|x| x.chars().rev().collect::<String>()); + + let mut ident = fragments.clone().next().unwrap(); + for (ref prev, ref curr) in fragments.tuple_windows() { + if [prev, curr] + .iter() + .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase()) + { + ident.push_str(&curr.to_ascii_lowercase()); + } else { + ident.push_str(curr); + } + } + ident +} + +fn check_ident(cx: &EarlyContext<'_>, ident: &Ident) { + let span = ident.span; + let ident = &ident.as_str(); + let corrected = correct_ident(ident); + if ident != &corrected { + span_lint_and_sugg( + cx, + UPPER_CASE_ACRONYMS, + span, + &format!("name `{}` contains a capitalized acronym", ident), + "consider making the acronym lowercase, except the initial letter", + corrected, + Applicability::MaybeIncorrect, + ) + } +} + +impl EarlyLintPass for UpperCaseAcronyms { + fn check_item(&mut self, cx: &EarlyContext<'_>, it: &Item) { + if_chain! { + if !in_external_macro(cx.sess(), it.span); + if matches!( + it.kind, + ItemKind::TyAlias(..) | ItemKind::Enum(..) | ItemKind::Struct(..) | ItemKind::Trait(..) + ); + then { + check_ident(cx, &it.ident); + } + } + } + + fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &Variant) { + check_ident(cx, &v.ident); + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs index 32d7840a451..b5a8300376c 100644 --- a/src/tools/clippy/clippy_lints/src/utils/conf.rs +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -127,6 +127,7 @@ define_Conf! { "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", + "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", diff --git a/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs b/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs index a7a6b5855b7..6caa04f651f 100644 --- a/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs +++ b/src/tools/clippy/clippy_lints/src/utils/diagnostics.rs @@ -186,8 +186,6 @@ pub fn span_lint_hir_and_then( /// | /// = note: `-D fold-any` implied by `-D warnings` /// ``` - -#[allow(clippy::unknown_clippy_lints)] #[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))] pub fn span_lint_and_sugg<'a, T: LintContext>( cx: &'a T, diff --git a/src/tools/clippy/clippy_lints/src/utils/higher.rs b/src/tools/clippy/clippy_lints/src/utils/higher.rs index 42ab9a1e7d2..340d340d6d3 100644 --- a/src/tools/clippy/clippy_lints/src/utils/higher.rs +++ b/src/tools/clippy/clippy_lints/src/utils/higher.rs @@ -158,8 +158,7 @@ pub fn for_loop<'tcx>( /// `while cond { body }` becomes `(cond, body)`. pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> { if_chain! { - if let hir::ExprKind::Loop(block, _, hir::LoopSource::While, _) = &expr.kind; - if let hir::Block { expr: Some(expr), .. } = &**block; + if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind; if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind; if let hir::ExprKind::DropTemps(cond) = &cond.kind; if let [hir::Arm { body, .. }, ..] = &arms[..]; diff --git a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs index 6066383f2ef..c5870dc5124 100644 --- a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs +++ b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs @@ -24,6 +24,7 @@ pub struct SpanlessEq<'a, 'tcx> { cx: &'a LateContext<'tcx>, maybe_typeck_results: Option<&'tcx TypeckResults<'tcx>>, allow_side_effects: bool, + expr_fallback: Option<Box<dyn Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a>>, } impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { @@ -32,6 +33,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { cx, maybe_typeck_results: cx.maybe_typeck_results(), allow_side_effects: true, + expr_fallback: None, } } @@ -43,6 +45,13 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { } } + pub fn expr_fallback(self, expr_fallback: impl Fn(&Expr<'_>, &Expr<'_>) -> bool + 'a) -> Self { + Self { + expr_fallback: Some(Box::new(expr_fallback)), + ..self + } + } + /// Checks whether two statements are the same. pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { match (&left.kind, &right.kind) { @@ -81,7 +90,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { } } - match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) { + let is_eq = match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) { (&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => { lb == rb && l_mut == r_mut && self.eq_expr(le, re) }, @@ -158,7 +167,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r), (&ExprKind::DropTemps(ref le), &ExprKind::DropTemps(ref re)) => self.eq_expr(le, re), _ => false, - } + }; + is_eq || self.expr_fallback.as_ref().map_or(false, |f| f(left, right)) } fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs index 0bbb6a52a20..d0db3a67533 100644 --- a/src/tools/clippy/clippy_lints/src/utils/mod.rs +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -31,7 +31,6 @@ pub use self::hir_utils::{both, eq_expr_value, over, SpanlessEq, SpanlessHash}; use std::borrow::Cow; use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; -use std::mem; use if_chain::if_chain; use rustc_ast::ast::{self, Attribute, LitKind}; @@ -39,7 +38,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE}; +use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::Node; use rustc_hir::{ @@ -48,6 +47,7 @@ use rustc_hir::{ }; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, Level, Lint, LintContext}; +use rustc_middle::hir::exports::Export; use rustc_middle::hir::map::Map; use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable}; @@ -308,65 +308,43 @@ pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool { } /// Gets the definition associated to a path. -pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> { - let crates = cx.tcx.crates(); - let krate = crates - .iter() - .find(|&&krate| cx.tcx.crate_name(krate).as_str() == path[0]); - if let Some(krate) = krate { - let krate = DefId { - krate: *krate, - index: CRATE_DEF_INDEX, - }; - let mut current_item = None; - let mut items = cx.tcx.item_children(krate); - let mut path_it = path.iter().skip(1).peekable(); - - loop { - let segment = match path_it.next() { - Some(segment) => segment, - None => return None, - }; - - // `get_def_path` seems to generate these empty segments for extern blocks. - // We can just ignore them. - if segment.is_empty() { - continue; - } - - let result = SmallVec::<[_; 8]>::new(); - for item in mem::replace(&mut items, cx.tcx.arena.alloc_slice(&result)).iter() { - if item.ident.name.as_str() == *segment { - if path_it.peek().is_none() { - return Some(item.res); - } - - current_item = Some(item); - items = cx.tcx.item_children(item.res.def_id()); - break; - } - } +#[allow(clippy::shadow_unrelated)] // false positive #6563 +pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<Res> { + fn item_child_by_name<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, name: &str) -> Option<&'tcx Export<HirId>> { + tcx.item_children(def_id) + .iter() + .find(|item| item.ident.name.as_str() == name) + } - // The segment isn't a child_item. - // Try to find it under an inherent impl. - if_chain! { - if path_it.peek().is_none(); - if let Some(current_item) = current_item; - let item_def_id = current_item.res.def_id(); - if cx.tcx.def_kind(item_def_id) == DefKind::Struct; - then { - // Bad `find_map` suggestion. See #4193. - #[allow(clippy::find_map)] - return cx.tcx.inherent_impls(item_def_id).iter() - .flat_map(|&impl_def_id| cx.tcx.item_children(impl_def_id)) - .find(|item| item.ident.name.as_str() == *segment) - .map(|item| item.res); - } + let (krate, first, path) = match *path { + [krate, first, ref path @ ..] => (krate, first, path), + _ => return None, + }; + let tcx = cx.tcx; + let crates = tcx.crates(); + let krate = crates.iter().find(|&&num| tcx.crate_name(num).as_str() == krate)?; + let first = item_child_by_name(tcx, krate.as_def_id(), first)?; + let last = path + .iter() + .copied() + // `get_def_path` seems to generate these empty segments for extern blocks. + // We can just ignore them. + .filter(|segment| !segment.is_empty()) + // for each segment, find the child item + .try_fold(first, |item, segment| { + let def_id = item.res.def_id(); + if let Some(item) = item_child_by_name(tcx, def_id, segment) { + Some(item) + } else if matches!(item.res, Res::Def(DefKind::Enum | DefKind::Struct, _)) { + // it is not a child item so check inherent impl items + tcx.inherent_impls(def_id) + .iter() + .find_map(|&impl_def_id| item_child_by_name(tcx, impl_def_id, segment)) + } else { + None } - } - } else { - None - } + })?; + Some(last.res) } /// Convenience function to get the `DefId` of a trait by path. @@ -1134,8 +1112,7 @@ pub fn is_self(slf: &Param<'_>) -> bool { pub fn is_self_ty(slf: &hir::Ty<'_>) -> bool { if_chain! { - if let TyKind::Path(ref qp) = slf.kind; - if let QPath::Resolved(None, ref path) = *qp; + if let TyKind::Path(QPath::Resolved(None, ref path)) = slf.kind; if let Res::SelfTy(..) = path.res; then { return true @@ -1655,6 +1632,44 @@ where match_expr_list } +/// Peels off all references on the pattern. Returns the underlying pattern and the number of +/// references removed. +pub fn peel_hir_pat_refs(pat: &'a Pat<'a>) -> (&'a Pat<'a>, usize) { + fn peel(pat: &'a Pat<'a>, count: usize) -> (&'a Pat<'a>, usize) { + if let PatKind::Ref(pat, _) = pat.kind { + peel(pat, count + 1) + } else { + (pat, count) + } + } + peel(pat, 0) +} + +/// Peels off up to the given number of references on the expression. Returns the underlying +/// expression and the number of references removed. +pub fn peel_n_hir_expr_refs(expr: &'a Expr<'a>, count: usize) -> (&'a Expr<'a>, usize) { + fn f(expr: &'a Expr<'a>, count: usize, target: usize) -> (&'a Expr<'a>, usize) { + match expr.kind { + ExprKind::AddrOf(_, _, expr) if count != target => f(expr, count + 1, target), + _ => (expr, count), + } + } + f(expr, 0, count) +} + +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_mid_ty_refs(ty: Ty<'_>) -> (Ty<'_>, usize) { + fn peel(ty: Ty<'_>, count: usize) -> (Ty<'_>, usize) { + if let ty::Ref(_, ty, _) = ty.kind() { + peel(ty, count + 1) + } else { + (ty, count) + } + } + peel(ty, 0) +} + #[macro_export] macro_rules! unwrap_cargo_metadata { ($cx: ident, $lint: ident, $deps: expr) => {{ diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index af324f831df..6358104eeda 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -2,7 +2,8 @@ use std::borrow::Cow; use std::ops::Range; use crate::utils::{snippet_with_applicability, span_lint, span_lint_and_sugg, span_lint_and_then}; -use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, MacCall, StrLit, StrStyle}; +use if_chain::if_chain; +use rustc_ast::ast::{Expr, ExprKind, Item, ItemKind, LitKind, MacCall, StrLit, StrStyle}; use rustc_ast::token; use rustc_ast::tokenstream::TokenStream; use rustc_errors::Applicability; @@ -437,7 +438,7 @@ impl Write { return (Some(fmtstr), None); }; match &token_expr.kind { - ExprKind::Lit(_) => { + ExprKind::Lit(lit) if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)) => { let mut all_simple = true; let mut seen = false; for arg in &args { @@ -457,8 +458,11 @@ impl Write { idx += 1; }, ExprKind::Assign(lhs, rhs, _) => { - if let ExprKind::Lit(_) = rhs.kind { - if let ExprKind::Path(_, p) = &lhs.kind { + if_chain! { + if let ExprKind::Lit(ref lit) = rhs.kind; + if !matches!(lit.kind, LitKind::Int(..) | LitKind::Float(..)); + if let ExprKind::Path(_, p) = &lhs.kind; + then { let mut all_simple = true; let mut seen = false; for arg in &args { diff --git a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs index 9761e822a7a..319b85ac42a 100644 --- a/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs +++ b/src/tools/clippy/clippy_lints/src/zero_sized_map_values.rs @@ -6,7 +6,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_target::abi::LayoutOf as _; use rustc_typeck::hir_ty_to_ty; -use crate::utils::{is_type_diagnostic_item, match_type, paths, span_lint_and_help}; +use crate::utils::{is_normalizable, is_type_diagnostic_item, match_type, paths, span_lint_and_help}; declare_clippy_lint! { /// **What it does:** Checks for maps with zero-sized value types anywhere in the code. @@ -50,6 +50,8 @@ impl LateLintPass<'_> for ZeroSizedMapValues { if is_type_diagnostic_item(cx, ty, sym!(hashmap_type)) || match_type(cx, ty, &paths::BTREEMAP); if let Adt(_, ref substs) = ty.kind(); let ty = substs.type_at(1); + // Do this to prevent `layout_of` crashing, being unable to fully normalize `ty`. + if is_normalizable(cx, cx.param_env, ty); if let Ok(layout) = cx.layout_of(ty); if layout.is_zst(); then { |
