diff options
| author | Eduardo Broto <ebroto@tutanota.com> | 2020-10-28 23:36:07 +0100 |
|---|---|---|
| committer | Eduardo Broto <ebroto@tutanota.com> | 2020-10-28 23:36:07 +0100 |
| commit | 77da566cc6fcc2e9debf5bc0edfa34b0f296df69 (patch) | |
| tree | 1af9739f3a1ceee12fc3aa743a0d2b4d30589c7d /src/tools/clippy/clippy_lints | |
| parent | 3dddf6ac1e194deb3d927064e7c6d50bc9325dd0 (diff) | |
| parent | 645ef505da378b6f810b1567806d1bcc2856395f (diff) | |
| download | rust-77da566cc6fcc2e9debf5bc0edfa34b0f296df69.tar.gz rust-77da566cc6fcc2e9debf5bc0edfa34b0f296df69.zip | |
Merge commit '645ef505da378b6f810b1567806d1bcc2856395f' into clippyup
Diffstat (limited to 'src/tools/clippy/clippy_lints')
29 files changed, 1308 insertions, 556 deletions
diff --git a/src/tools/clippy/clippy_lints/src/await_holding_lock.rs b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs index 367534499fd..fcebb54c6c2 100644 --- a/src/tools/clippy/clippy_lints/src/await_holding_lock.rs +++ b/src/tools/clippy/clippy_lints/src/await_holding_invalid.rs @@ -45,13 +45,52 @@ declare_clippy_lint! { /// } /// ``` pub AWAIT_HOLDING_LOCK, - pedantic, + correctness, "Inside an async function, holding a MutexGuard while calling await" } -declare_lint_pass!(AwaitHoldingLock => [AWAIT_HOLDING_LOCK]); +declare_clippy_lint! { + /// **What it does:** Checks for calls to await while holding a + /// `RefCell` `Ref` or `RefMut`. + /// + /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access + /// 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. + /// + /// **Example:** + /// + /// ```rust,ignore + /// use std::cell::RefCell; + /// + /// async fn foo(x: &RefCell<u32>) { + /// let b = x.borrow_mut()(); + /// *ref += 1; + /// bar.await; + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// use std::cell::RefCell; + /// + /// async fn foo(x: &RefCell<u32>) { + /// { + /// let b = x.borrow_mut(); + /// *ref += 1; + /// } + /// bar.await; + /// } + /// ``` + pub AWAIT_HOLDING_REFCELL_REF, + correctness, + "Inside an async function, holding a RefCell ref while calling await" +} -impl LateLintPass<'_> for AwaitHoldingLock { +declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]); + +impl LateLintPass<'_> for AwaitHolding { fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { use AsyncGeneratorKind::{Block, Closure, Fn}; if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind { @@ -78,6 +117,16 @@ fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorType "these are all the await points this lock is held through", ); } + if is_refcell_ref(cx, adt.did) { + span_lint_and_note( + cx, + AWAIT_HOLDING_REFCELL_REF, + ty_cause.span, + "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await.", + ty_cause.scope_span.or(Some(span)), + "these are all the await points this ref is held through", + ); + } } } } @@ -90,3 +139,7 @@ fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool { || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD) || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD) } + +fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool { + match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT) +} diff --git a/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs index 1b73ced89b3..736730d4084 100644 --- a/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs +++ b/src/tools/clippy/clippy_lints/src/blocks_in_if_conditions.rs @@ -28,7 +28,6 @@ declare_clippy_lint! { /// /// ```rust /// # fn somefunc() -> bool { true }; - /// /// // Bad /// if { let x = somefunc(); x } { /* ... */ } /// diff --git a/src/tools/clippy/clippy_lints/src/copies.rs b/src/tools/clippy/clippy_lints/src/copies.rs index 6c969c3ead0..46ce92ea6d7 100644 --- a/src/tools/clippy/clippy_lints/src/copies.rs +++ b/src/tools/clippy/clippy_lints/src/copies.rs @@ -1,13 +1,8 @@ -use crate::utils::{eq_expr_value, in_macro, SpanlessEq, SpanlessHash}; -use crate::utils::{get_parent_expr, higher, if_sequence, snippet, span_lint_and_note, span_lint_and_then}; -use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Arm, Block, Expr, ExprKind, MatchSource, Pat, PatKind}; +use crate::utils::{eq_expr_value, in_macro, search_same, SpanlessEq, SpanlessHash}; +use crate::utils::{get_parent_expr, higher, if_sequence, span_lint_and_note}; +use rustc_hir::{Block, Expr}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{Ty, TyS}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::symbol::Symbol; -use std::collections::hash_map::Entry; -use std::hash::BuildHasherDefault; declare_clippy_lint! { /// **What it does:** Checks for consecutive `if`s with the same condition. @@ -108,48 +103,7 @@ declare_clippy_lint! { "`if` with the same `then` and `else` blocks" } -declare_clippy_lint! { - /// **What it does:** Checks for `match` with identical arm bodies. - /// - /// **Why is this bad?** This is probably a copy & paste error. If arm bodies - /// are the same on purpose, you can factor them - /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). - /// - /// **Known problems:** False positive possible with order dependent `match` - /// (see issue - /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). - /// - /// **Example:** - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => bar(), // <= oops - /// } - /// ``` - /// - /// This should probably be - /// ```rust,ignore - /// match foo { - /// Bar => bar(), - /// Quz => quz(), - /// Baz => baz(), // <= fixed - /// } - /// ``` - /// - /// or if the original code was not a typo: - /// ```rust,ignore - /// match foo { - /// Bar | Baz => bar(), // <= shows the intent better - /// Quz => quz(), - /// } - /// ``` - pub MATCH_SAME_ARMS, - pedantic, - "`match` with identical arm bodies" -} - -declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE, MATCH_SAME_ARMS]); +declare_lint_pass!(CopyAndPaste => [IFS_SAME_COND, SAME_FUNCTIONS_IN_IF_CONDITION, IF_SAME_THEN_ELSE]); impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -167,7 +121,6 @@ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { lint_same_then_else(cx, &blocks); lint_same_cond(cx, &conds); lint_same_fns_in_if_cond(cx, &conds); - lint_match_arms(cx, expr); } } } @@ -243,122 +196,6 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { } } -/// Implementation of `MATCH_SAME_ARMS`. -fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { - fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool { - lhs.len() == rhs.len() - && lhs - .iter() - .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty))) - } - - if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { - let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(&arm.body); - h.finish() - }; - - let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { - let min_index = usize::min(lindex, rindex); - let max_index = usize::max(lindex, rindex); - - // Arms with a guard are ignored, those can’t always be merged together - // This is also the case for arms in-between each there is an arm with a guard - (min_index..=max_index).all(|index| arms[index].guard.is_none()) && - SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) && - // all patterns should have the same bindings - same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat)) - }; - - let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); - for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { - span_lint_and_then( - cx, - MATCH_SAME_ARMS, - j.body.span, - "this `match` has identical arm bodies", - |diag| { - diag.span_note(i.body.span, "same as this"); - - // Note: this does not use `span_suggestion` on purpose: - // there is no clean way - // to remove the other arm. Building a span and suggest to replace it to "" - // makes an even more confusing error message. Also in order not to make up a - // span for the whole pattern, the suggestion is only shown when there is only - // one pattern. The user should know about `|` if they are already using it… - - let lhs = snippet(cx, i.pat.span, "<pat1>"); - let rhs = snippet(cx, j.pat.span, "<pat2>"); - - if let PatKind::Wild = j.pat.kind { - // if the last arm is _, then i could be integrated into _ - // note that i.pat cannot be _, because that would mean that we're - // hiding all the subsequent arms, and rust won't compile - diag.span_note( - i.body.span, - &format!( - "`{}` has the same arm body as the `_` wildcard, consider removing it", - lhs - ), - ); - } else { - diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs)); - } - }, - ); - } - } -} - -/// Returns the list of bindings in a pattern. -fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> { - fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) { - match pat.kind { - PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map), - PatKind::TupleStruct(_, pats, _) => { - for pat in pats { - bindings_impl(cx, pat, map); - } - }, - PatKind::Binding(.., ident, ref as_pat) => { - if let Entry::Vacant(v) = map.entry(ident.name) { - v.insert(cx.typeck_results().pat_ty(pat)); - } - if let Some(ref as_pat) = *as_pat { - bindings_impl(cx, as_pat, map); - } - }, - PatKind::Or(fields) | PatKind::Tuple(fields, _) => { - for pat in fields { - bindings_impl(cx, pat, map); - } - }, - PatKind::Struct(_, fields, _) => { - for pat in fields { - bindings_impl(cx, &pat.pat, map); - } - }, - PatKind::Slice(lhs, ref mid, rhs) => { - for pat in lhs { - bindings_impl(cx, pat, map); - } - if let Some(ref mid) = *mid { - bindings_impl(cx, mid, map); - } - for pat in rhs { - bindings_impl(cx, pat, map); - } - }, - PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (), - } - } - - let mut result = FxHashMap::default(); - bindings_impl(cx, pat, &mut result); - result -} - fn search_same_sequenced<T, Eq>(exprs: &[T], eq: Eq) -> Option<(&T, &T)> where Eq: Fn(&T, &T) -> bool, @@ -370,47 +207,3 @@ where } None } - -fn search_common_cases<'a, T, Eq>(exprs: &'a [T], eq: &Eq) -> Option<(&'a T, &'a T)> -where - Eq: Fn(&T, &T) -> bool, -{ - if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { - Some((&exprs[0], &exprs[1])) - } else { - None - } -} - -fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> -where - Hash: Fn(&T) -> u64, - Eq: Fn(&T, &T) -> bool, -{ - if let Some(expr) = search_common_cases(&exprs, &eq) { - return vec![expr]; - } - - let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); - - let mut map: FxHashMap<_, Vec<&_>> = - FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); - - for expr in exprs { - match map.entry(hash(expr)) { - Entry::Occupied(mut o) => { - for o in o.get() { - if eq(o, expr) { - match_expr_list.push((o, expr)); - } - } - o.get_mut().push(expr); - }, - Entry::Vacant(v) => { - v.insert(vec![expr]); - }, - } - } - - match_expr_list -} diff --git a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs index c5884361dff..461c6e31d3e 100644 --- a/src/tools/clippy/clippy_lints/src/deprecated_lints.rs +++ b/src/tools/clippy/clippy_lints/src/deprecated_lints.rs @@ -172,3 +172,12 @@ declare_deprecated_lint! { pub DROP_BOUNDS, "this lint has been uplifted to rustc and is now called `drop_bounds`" } + +declare_deprecated_lint! { + /// **What it does:** Nothing. This lint has been deprecated. + /// + /// **Deprecation reason:** This lint has been uplifted to rustc and is now called + /// `temporary_cstring_as_ptr`. + pub TEMPORARY_CSTRING_AS_PTR, + "this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`" +} diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs index bf8e030cc29..c75efc6e99f 100644 --- a/src/tools/clippy/clippy_lints/src/derive.rs +++ b/src/tools/clippy/clippy_lints/src/derive.rs @@ -1,6 +1,6 @@ use crate::utils::paths; use crate::utils::{ - get_trait_def_id, is_allowed, is_automatically_derived, is_copy, match_path, span_lint_and_help, + get_trait_def_id, is_allowed, is_automatically_derived, is_copy, match_def_path, match_path, span_lint_and_help, span_lint_and_note, span_lint_and_then, }; use if_chain::if_chain; @@ -193,10 +193,9 @@ fn check_hash_peq<'tcx>( hash_is_automatically_derived: bool, ) { if_chain! { - if match_path(&trait_ref.path, &paths::HASH); if let Some(peq_trait_def_id) = cx.tcx.lang_items().eq_trait(); - if let Some(def_id) = &trait_ref.trait_def_id(); - if !def_id.is_local(); + if let Some(def_id) = trait_ref.trait_def_id(); + if match_def_path(cx, def_id, &paths::HASH); then { // Look for the PartialEq implementations for `ty` cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { @@ -352,7 +351,8 @@ fn check_unsafe_derive_deserialize<'tcx>( } if_chain! { - if match_path(&trait_ref.path, &paths::SERDE_DESERIALIZE); + if let Some(trait_def_id) = trait_ref.trait_def_id(); + if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE); if let ty::Adt(def, _) = ty.kind(); if let Some(local_def_id) = def.did.as_local(); let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id); diff --git a/src/tools/clippy/clippy_lints/src/escape.rs b/src/tools/clippy/clippy_lints/src/escape.rs index 8b022912573..1bf3b810fb5 100644 --- a/src/tools/clippy/clippy_lints/src/escape.rs +++ b/src/tools/clippy/clippy_lints/src/escape.rs @@ -29,7 +29,6 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// # fn foo(bar: usize) {} - /// /// // Bad /// let x = Box::new(1); /// foo(*x); diff --git a/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs index 69818b4d3c6..c1c08597ee6 100644 --- a/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs +++ b/src/tools/clippy/clippy_lints/src/float_equality_without_abs.rs @@ -1,7 +1,8 @@ -use crate::utils::{match_qpath, paths, span_lint_and_then, sugg}; +use crate::utils::{match_def_path, paths, span_lint_and_then, sugg}; use if_chain::if_chain; use rustc_ast::util::parser::AssocOp; use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -76,7 +77,8 @@ impl<'tcx> LateLintPass<'tcx> for FloatEqualityWithoutAbs { // right hand side matches either f32::EPSILON or f64::EPSILON if let ExprKind::Path(ref epsilon_path) = rhs.kind; - if match_qpath(epsilon_path, &paths::F32_EPSILON) || match_qpath(epsilon_path, &paths::F64_EPSILON); + if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id); + if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON); // values of the substractions on the left hand side are of the type float let t_val_l = cx.typeck_results().expr_ty(val_l); diff --git a/src/tools/clippy/clippy_lints/src/functions.rs b/src/tools/clippy/clippy_lints/src/functions.rs index fd45a6da61c..9c0efef95de 100644 --- a/src/tools/clippy/clippy_lints/src/functions.rs +++ b/src/tools/clippy/clippy_lints/src/functions.rs @@ -579,9 +579,8 @@ fn is_mutable_pat(cx: &LateContext<'_>, pat: &hir::Pat<'_>, tys: &mut FxHashSet< if let hir::PatKind::Wild = pat.kind { return false; // ignore `_` patterns } - let def_id = pat.hir_id.owner.to_def_id(); - if cx.tcx.has_typeck_results(def_id) { - is_mutable_ty(cx, &cx.tcx.typeck(def_id.expect_local()).pat_ty(pat), pat.span, tys) + if cx.tcx.has_typeck_results(pat.hir_id.owner.to_def_id()) { + is_mutable_ty(cx, &cx.tcx.typeck(pat.hir_id.owner).pat_ty(pat), pat.span, tys) } else { false } @@ -694,11 +693,10 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for StaticMutVisitor<'a, 'tcx> { Call(_, args) | MethodCall(_, _, args, _) => { let mut tys = FxHashSet::default(); for arg in args { - let def_id = arg.hir_id.owner.to_def_id(); - if self.cx.tcx.has_typeck_results(def_id) + if self.cx.tcx.has_typeck_results(arg.hir_id.owner.to_def_id()) && is_mutable_ty( self.cx, - self.cx.tcx.typeck(def_id.expect_local()).expr_ty(arg), + self.cx.tcx.typeck(arg.hir_id.owner).expr_ty(arg), arg.span, &mut tys, ) diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index b5ca63cefec..2d374969846 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -160,7 +160,7 @@ mod assign_ops; mod async_yields_async; mod atomic_ordering; mod attrs; -mod await_holding_lock; +mod await_holding_invalid; mod bit_mask; mod blacklisted_name; mod blocks_in_if_conditions; @@ -255,6 +255,7 @@ mod modulo_arithmetic; mod multiple_crate_versions; mod mut_key; mod mut_mut; +mod mut_mutex_lock; mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; @@ -278,6 +279,7 @@ mod overflow_check_conditional; mod panic_in_result_fn; mod panic_unimplemented; mod partialeq_ne_impl; +mod pass_by_ref_or_value; mod path_buf_push_overwrite; mod pattern_type_mismatch; mod precedence; @@ -311,9 +313,9 @@ mod to_string_in_display; mod trait_bounds; mod transmute; mod transmuting_null; -mod trivially_copy_pass_by_ref; mod try_err; mod types; +mod undropped_manually_drops; mod unicode; mod unit_return_expecting_ord; mod unnamed_address; @@ -486,6 +488,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: "clippy::drop_bounds", "this lint has been uplifted to rustc and is now called `drop_bounds`", ); + store.register_removed( + "clippy::temporary_cstring_as_ptr", + "this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr`", + ); // 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` @@ -509,7 +515,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &attrs::MISMATCHED_TARGET_OS, &attrs::UNKNOWN_CLIPPY_LINTS, &attrs::USELESS_ATTRIBUTE, - &await_holding_lock::AWAIT_HOLDING_LOCK, + &await_holding_invalid::AWAIT_HOLDING_LOCK, + &await_holding_invalid::AWAIT_HOLDING_REFCELL_REF, &bit_mask::BAD_BIT_MASK, &bit_mask::INEFFECTIVE_BIT_MASK, &bit_mask::VERBOSE_BIT_MASK, @@ -525,7 +532,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &comparison_chain::COMPARISON_CHAIN, &copies::IFS_SAME_COND, &copies::IF_SAME_THEN_ELSE, - &copies::MATCH_SAME_ARMS, &copies::SAME_FUNCTIONS_IN_IF_CONDITION, ©_iterator::COPY_ITERATOR, &create_dir::CREATE_DIR, @@ -620,6 +626,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &literal_representation::LARGE_DIGIT_GROUPS, &literal_representation::MISTYPED_LITERAL_SUFFIXES, &literal_representation::UNREADABLE_LITERAL, + &literal_representation::UNUSUAL_BYTE_GROUPINGS, &loops::EMPTY_LOOP, &loops::EXPLICIT_COUNTER_LOOP, &loops::EXPLICIT_INTO_ITER_LOOP, @@ -633,6 +640,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &loops::NEEDLESS_RANGE_LOOP, &loops::NEVER_LOOP, &loops::SAME_ITEM_PUSH, + &loops::SINGLE_ELEMENT_LOOP, &loops::WHILE_IMMUTABLE_CONDITION, &loops::WHILE_LET_LOOP, &loops::WHILE_LET_ON_ITERATOR, @@ -654,6 +662,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &matches::MATCH_LIKE_MATCHES_MACRO, &matches::MATCH_OVERLAPPING_ARM, &matches::MATCH_REF_PATS, + &matches::MATCH_SAME_ARMS, &matches::MATCH_SINGLE_BINDING, &matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS, &matches::MATCH_WILD_ERR_ARM, @@ -742,6 +751,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &multiple_crate_versions::MULTIPLE_CRATE_VERSIONS, &mut_key::MUTABLE_KEY_TYPE, &mut_mut::MUT_MUT, + &mut_mutex_lock::MUT_MUTEX_LOCK, &mut_reference::UNNECESSARY_MUT_PASSED, &mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL, &mutex_atomic::MUTEX_ATOMIC, @@ -775,6 +785,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &panic_unimplemented::UNIMPLEMENTED, &panic_unimplemented::UNREACHABLE, &partialeq_ne_impl::PARTIALEQ_NE_IMPL, + &pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE, + &pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF, &path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE, &pattern_type_mismatch::PATTERN_TYPE_MISMATCH, &precedence::PRECEDENCE, @@ -784,6 +796,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &ptr_eq::PTR_EQ, &ptr_offset_with_cast::PTR_OFFSET_WITH_CAST, &question_mark::QUESTION_MARK, + &ranges::MANUAL_RANGE_CONTAINS, &ranges::RANGE_MINUS_ONE, &ranges::RANGE_PLUS_ONE, &ranges::RANGE_ZIP_WITH_LEN, @@ -834,7 +847,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &transmute::USELESS_TRANSMUTE, &transmute::WRONG_TRANSMUTE, &transmuting_null::TRANSMUTING_NULL, - &trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF, &try_err::TRY_ERR, &types::ABSURD_EXTREME_COMPARISONS, &types::BORROWED_BOX, @@ -861,6 +873,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &types::UNIT_CMP, &types::UNNECESSARY_CAST, &types::VEC_BOX, + &undropped_manually_drops::UNDROPPED_MANUALLY_DROPS, &unicode::INVISIBLE_CHARACTERS, &unicode::NON_ASCII_LITERAL, &unicode::UNICODE_NOT_NFC, @@ -882,6 +895,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS, &utils::internal_lints::COMPILER_LINT_FUNCTIONS, &utils::internal_lints::DEFAULT_LINT, + &utils::internal_lints::INVALID_PATHS, &utils::internal_lints::LINT_WITHOUT_LINT_PASS, &utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM, &utils::internal_lints::OUTER_EXPN_EXPN_DATA, @@ -904,11 +918,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: ]); // end register lints, do not remove this comment, it’s used in `update_lints` - store.register_late_pass(|| box await_holding_lock::AwaitHoldingLock); + store.register_late_pass(|| box await_holding_invalid::AwaitHolding); store.register_late_pass(|| box serde_api::SerdeAPI); store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new()); store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default()); store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass); + store.register_late_pass(|| box utils::internal_lints::InvalidPaths); store.register_late_pass(|| box utils::inspector::DeepCodeInspector); store.register_late_pass(|| box utils::author::Author); let vec_box_size_threshold = conf.vec_box_size_threshold; @@ -1008,11 +1023,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)); store.register_late_pass(|| box explicit_write::ExplicitWrite); store.register_late_pass(|| box needless_pass_by_value::NeedlessPassByValue); - let trivially_copy_pass_by_ref = trivially_copy_pass_by_ref::TriviallyCopyPassByRef::new( + let pass_by_ref_or_value = pass_by_ref_or_value::PassByRefOrValue::new( conf.trivial_copy_size_limit, + conf.pass_by_value_size_limit, &sess.target, ); - store.register_late_pass(move || box trivially_copy_pass_by_ref); + store.register_late_pass(move || box pass_by_ref_or_value); store.register_late_pass(|| box try_err::TryErr); store.register_late_pass(|| box use_self::UseSelf); store.register_late_pass(|| box bytecount::ByteCount); @@ -1108,6 +1124,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box future_not_send::FutureNotSend); store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls); store.register_late_pass(|| box if_let_mutex::IfLetMutex); + store.register_late_pass(|| box mut_mutex_lock::MutMutexLock); store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems); store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive); store.register_late_pass(|| box manual_async_fn::ManualAsyncFn); @@ -1136,6 +1153,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods)); store.register_early_pass(|| box asm_syntax::InlineAsmX86AttSyntax); store.register_early_pass(|| box asm_syntax::InlineAsmX86IntelSyntax); + store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ @@ -1187,10 +1205,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![ LintId::of(&attrs::INLINE_ALWAYS), - LintId::of(&await_holding_lock::AWAIT_HOLDING_LOCK), LintId::of(&bit_mask::VERBOSE_BIT_MASK), LintId::of(&checked_conversions::CHECKED_CONVERSIONS), - LintId::of(&copies::MATCH_SAME_ARMS), LintId::of(&copies::SAME_FUNCTIONS_IN_IF_CONDITION), LintId::of(©_iterator::COPY_ITERATOR), LintId::of(&default_trait_access::DEFAULT_TRAIT_ACCESS), @@ -1220,6 +1236,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&map_err_ignore::MAP_ERR_IGNORE), LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS), LintId::of(&matches::MATCH_BOOL), + LintId::of(&matches::MATCH_SAME_ARMS), LintId::of(&matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS), LintId::of(&matches::MATCH_WILD_ERR_ARM), LintId::of(&matches::SINGLE_MATCH_ELSE), @@ -1236,13 +1253,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), LintId::of(&non_expressive_names::SIMILAR_NAMES), LintId::of(&option_if_let_else::OPTION_IF_LET_ELSE), + LintId::of(&pass_by_ref_or_value::LARGE_TYPES_PASSED_BY_VALUE), + LintId::of(&pass_by_ref_or_value::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(&ranges::RANGE_MINUS_ONE), LintId::of(&ranges::RANGE_PLUS_ONE), LintId::of(&shadow::SHADOW_UNRELATED), LintId::of(&strings::STRING_ADD_ASSIGN), LintId::of(&trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS), LintId::of(&trait_bounds::TYPE_REPETITION_IN_BOUNDS), - LintId::of(&trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF), LintId::of(&types::CAST_LOSSLESS), LintId::of(&types::CAST_POSSIBLE_TRUNCATION), LintId::of(&types::CAST_POSSIBLE_WRAP), @@ -1267,6 +1285,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS), LintId::of(&utils::internal_lints::COMPILER_LINT_FUNCTIONS), LintId::of(&utils::internal_lints::DEFAULT_LINT), + LintId::of(&utils::internal_lints::INVALID_PATHS), LintId::of(&utils::internal_lints::LINT_WITHOUT_LINT_PASS), LintId::of(&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM), LintId::of(&utils::internal_lints::OUTER_EXPN_EXPN_DATA), @@ -1286,6 +1305,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&attrs::MISMATCHED_TARGET_OS), LintId::of(&attrs::UNKNOWN_CLIPPY_LINTS), LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(&bit_mask::BAD_BIT_MASK), LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(&blacklisted_name::BLACKLISTED_NAME), @@ -1351,6 +1372,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&lifetimes::NEEDLESS_LIFETIMES), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), LintId::of(&literal_representation::MISTYPED_LITERAL_SUFFIXES), + LintId::of(&literal_representation::UNUSUAL_BYTE_GROUPINGS), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::EXPLICIT_COUNTER_LOOP), LintId::of(&loops::FOR_KV_MAP), @@ -1362,6 +1384,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::NEEDLESS_RANGE_LOOP), LintId::of(&loops::NEVER_LOOP), LintId::of(&loops::SAME_ITEM_PUSH), + LintId::of(&loops::SINGLE_ELEMENT_LOOP), LintId::of(&loops::WHILE_IMMUTABLE_CONDITION), LintId::of(&loops::WHILE_LET_LOOP), LintId::of(&loops::WHILE_LET_ON_ITERATOR), @@ -1439,6 +1462,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&misc_early::UNNEEDED_WILDCARD_PATTERN), LintId::of(&misc_early::ZERO_PREFIXED_LITERAL), LintId::of(&mut_key::MUTABLE_KEY_TYPE), + LintId::of(&mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(&mutex_atomic::MUTEX_ATOMIC), LintId::of(&needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE), @@ -1467,6 +1491,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ptr_eq::PTR_EQ), LintId::of(&ptr_offset_with_cast::PTR_OFFSET_WITH_CAST), LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&ranges::MANUAL_RANGE_CONTAINS), LintId::of(&ranges::RANGE_ZIP_WITH_LEN), LintId::of(&ranges::REVERSED_EMPTY_RANGES), LintId::of(&redundant_clone::REDUNDANT_CLONE), @@ -1519,6 +1544,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::UNIT_CMP), LintId::of(&types::UNNECESSARY_CAST), LintId::of(&types::VEC_BOX), + LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(&unicode::INVISIBLE_CHARACTERS), LintId::of(&unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), @@ -1568,6 +1594,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY), LintId::of(&len_zero::LEN_ZERO), LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING), + LintId::of(&literal_representation::UNUSUAL_BYTE_GROUPINGS), LintId::of(&loops::EMPTY_LOOP), LintId::of(&loops::FOR_KV_MAP), LintId::of(&loops::NEEDLESS_RANGE_LOOP), @@ -1610,6 +1637,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&misc_early::DUPLICATE_UNDERSCORE_ARGUMENT), LintId::of(&misc_early::MIXED_CASE_HEX_LITERALS), LintId::of(&misc_early::REDUNDANT_PATTERN), + LintId::of(&mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(&mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(&neg_multiply::NEG_MULTIPLY), LintId::of(&new_without_default::NEW_WITHOUT_DEFAULT), @@ -1622,6 +1650,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&ptr::PTR_ARG), LintId::of(&ptr_eq::PTR_EQ), LintId::of(&question_mark::QUESTION_MARK), + LintId::of(&ranges::MANUAL_RANGE_CONTAINS), LintId::of(&redundant_field_names::REDUNDANT_FIELD_NAMES), LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES), LintId::of(®ex::TRIVIAL_REGEX), @@ -1662,6 +1691,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&lifetimes::NEEDLESS_LIFETIMES), LintId::of(&loops::EXPLICIT_COUNTER_LOOP), LintId::of(&loops::MUT_RANGE_BOUND), + LintId::of(&loops::SINGLE_ELEMENT_LOOP), LintId::of(&loops::WHILE_LET_LOOP), LintId::of(&manual_strip::MANUAL_STRIP), LintId::of(&manual_unwrap_or::MANUAL_UNWRAP_OR), @@ -1731,6 +1761,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&attrs::DEPRECATED_SEMVER), LintId::of(&attrs::MISMATCHED_TARGET_OS), LintId::of(&attrs::USELESS_ATTRIBUTE), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_LOCK), + LintId::of(&await_holding_invalid::AWAIT_HOLDING_REFCELL_REF), LintId::of(&bit_mask::BAD_BIT_MASK), LintId::of(&bit_mask::INEFFECTIVE_BIT_MASK), LintId::of(&booleans::LOGIC_BUG), @@ -1787,6 +1819,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&types::ABSURD_EXTREME_COMPARISONS), LintId::of(&types::CAST_REF_TO_MUT), LintId::of(&types::UNIT_CMP), + LintId::of(&undropped_manually_drops::UNDROPPED_MANUALLY_DROPS), LintId::of(&unicode::INVISIBLE_CHARACTERS), LintId::of(&unit_return_expecting_ord::UNIT_RETURN_EXPECTING_ORD), LintId::of(&unnamed_address::FN_ADDRESS_COMPARISONS), diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs index d7043e7bd8f..c8a5a9c9431 100644 --- a/src/tools/clippy/clippy_lints/src/lifetimes.rs +++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs @@ -414,7 +414,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl let mut visitor = RefVisitor::new(cx); // walk the type F, it may not contain LT refs walk_ty(&mut visitor, &pred.bounded_ty); - if !visitor.lts.is_empty() { + if !visitor.all_lts().is_empty() { return true; } // if the bounds define new lifetimes, they are fine to occur @@ -424,7 +424,9 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, where_clause: &'tcx WhereCl walk_param_bound(&mut visitor, bound); } // and check that all lifetimes are allowed - return visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)); + if visitor.all_lts().iter().any(|it| !allowed_lts.contains(it)) { + return true; + } }, WherePredicate::EqPredicate(ref pred) => { let mut visitor = RefVisitor::new(cx); diff --git a/src/tools/clippy/clippy_lints/src/literal_representation.rs b/src/tools/clippy/clippy_lints/src/literal_representation.rs index c54103b25c2..e8a741683da 100644 --- a/src/tools/clippy/clippy_lints/src/literal_representation.rs +++ b/src/tools/clippy/clippy_lints/src/literal_representation.rs @@ -83,6 +83,25 @@ declare_clippy_lint! { } declare_clippy_lint! { + /// **What it does:** Warns if hexadecimal or binary literals are not grouped + /// by nibble or byte. + /// + /// **Why is this bad?** Negatively impacts readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// let x: u32 = 0xFFF_FFF; + /// let y: u8 = 0b01_011_101; + /// ``` + pub UNUSUAL_BYTE_GROUPINGS, + style, + "binary or hex literals that aren't grouped by four" +} + +declare_clippy_lint! { /// **What it does:** Warns if the digits of an integral or floating-point /// constant are grouped into groups that /// are too large. @@ -125,6 +144,7 @@ enum WarningType { LargeDigitGroups, DecimalRepresentation, MistypedLiteralSuffix, + UnusualByteGroupings, } impl WarningType { @@ -175,6 +195,15 @@ impl WarningType { suggested_format, Applicability::MachineApplicable, ), + Self::UnusualByteGroupings => span_lint_and_sugg( + cx, + UNUSUAL_BYTE_GROUPINGS, + span, + "digits of hex or binary literal not grouped by four", + "consider", + suggested_format, + Applicability::MachineApplicable, + ), }; } } @@ -184,6 +213,7 @@ declare_lint_pass!(LiteralDigitGrouping => [ INCONSISTENT_DIGIT_GROUPING, LARGE_DIGIT_GROUPS, MISTYPED_LITERAL_SUFFIXES, + UNUSUAL_BYTE_GROUPINGS, ]); impl EarlyLintPass for LiteralDigitGrouping { @@ -217,9 +247,9 @@ impl LiteralDigitGrouping { let result = (|| { - let integral_group_size = Self::get_group_size(num_lit.integer.split('_'))?; + let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix)?; if let Some(fraction) = num_lit.fraction { - let fractional_group_size = Self::get_group_size(fraction.rsplit('_'))?; + let fractional_group_size = Self::get_group_size(fraction.rsplit('_'), num_lit.radix)?; let consistent = Self::parts_consistent(integral_group_size, fractional_group_size, @@ -229,6 +259,7 @@ impl LiteralDigitGrouping { return Err(WarningType::InconsistentDigitGrouping); }; } + Ok(()) })(); @@ -237,6 +268,7 @@ impl LiteralDigitGrouping { let should_warn = match warning_type { | WarningType::UnreadableLiteral | WarningType::InconsistentDigitGrouping + | WarningType::UnusualByteGroupings | WarningType::LargeDigitGroups => { !in_macro(lit.span) } @@ -331,11 +363,15 @@ impl LiteralDigitGrouping { /// Returns the size of the digit groups (or None if ungrouped) if successful, /// otherwise returns a `WarningType` for linting. - fn get_group_size<'a>(groups: impl Iterator<Item = &'a str>) -> Result<Option<usize>, WarningType> { + fn get_group_size<'a>(groups: impl Iterator<Item = &'a str>, radix: Radix) -> Result<Option<usize>, WarningType> { let mut groups = groups.map(str::len); let first = groups.next().expect("At least one group"); + if (radix == Radix::Binary || radix == Radix::Hexadecimal) && groups.any(|i| i != 4 && i != 2) { + return Err(WarningType::UnusualByteGroupings); + } + if let Some(second) = groups.next() { if !groups.all(|x| x == second) || first > second { Err(WarningType::InconsistentDigitGrouping) diff --git a/src/tools/clippy/clippy_lints/src/loops.rs b/src/tools/clippy/clippy_lints/src/loops.rs index 63d7e3176b1..c3f75f283f4 100644 --- a/src/tools/clippy/clippy_lints/src/loops.rs +++ b/src/tools/clippy/clippy_lints/src/loops.rs @@ -4,9 +4,10 @@ use crate::utils::sugg::Sugg; use crate::utils::usage::{is_unused, mutated_variables}; use crate::utils::{ contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, - is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, match_trait_method, - match_type, match_var, multispan_sugg, qpath_res, snippet, snippet_with_applicability, snippet_with_macro_callsite, - span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq, + indent_of, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item, last_path_segment, + match_trait_method, match_type, match_var, multispan_sugg, qpath_res, single_segment_path, snippet, + snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, + span_lint_and_then, sugg, SpanlessEq, }; use if_chain::if_chain; use rustc_ast::ast; @@ -293,9 +294,24 @@ declare_clippy_lint! { declare_clippy_lint! { /// **What it does:** Checks for empty `loop` expressions. /// - /// **Why is this bad?** Those busy loops burn CPU cycles without doing - /// anything. Think of the environment and either block on something or at least - /// make the thread sleep for some microseconds. + /// **Why is this bad?** These busy loops burn CPU cycles without doing + /// anything. It is _almost always_ a better idea to `panic!` than to have + /// a busy loop. + /// + /// If panicking isn't possible, think of the environment and either: + /// - block on something + /// - sleep the thread for some microseconds + /// - yield or pause the thread + /// + /// For `std` targets, this can be done with + /// [`std::thread::sleep`](https://doc.rust-lang.org/std/thread/fn.sleep.html) + /// or [`std::thread::yield_now`](https://doc.rust-lang.org/std/thread/fn.yield_now.html). + /// + /// For `no_std` targets, doing this is more complicated, especially because + /// `#[panic_handler]`s can't panic. To stop/pause the thread, you will + /// probably need to invoke some target-specific intrinsic. Examples include: + /// - [`x86_64::instructions::hlt`](https://docs.rs/x86_64/0.12.2/x86_64/instructions/fn.hlt.html) + /// - [`cortex_m::asm::wfi`](https://docs.rs/cortex-m/0.6.3/cortex_m/asm/fn.wfi.html) /// /// **Known problems:** None. /// @@ -452,6 +468,31 @@ declare_clippy_lint! { "the same item is pushed inside of a for loop" } +declare_clippy_lint! { + /// **What it does:** Checks whether a for loop has a single element. + /// + /// **Why is this bad?** There is no reason to have a loop of a + /// single element. + /// **Known problems:** None + /// + /// **Example:** + /// ```rust + /// let item1 = 2; + /// for item in &[item1] { + /// println!("{}", item); + /// } + /// ``` + /// could be written as + /// ```rust + /// let item1 = 2; + /// let item = &item1; + /// println!("{}", item); + /// ``` + pub SINGLE_ELEMENT_LOOP, + complexity, + "there is no reason to have a single element loop" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, NEEDLESS_RANGE_LOOP, @@ -469,6 +510,7 @@ declare_lint_pass!(Loops => [ MUT_RANGE_BOUND, WHILE_IMMUTABLE_CONDITION, SAME_ITEM_PUSH, + SINGLE_ELEMENT_LOOP, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -502,13 +544,15 @@ impl<'tcx> LateLintPass<'tcx> for Loops { // (even if the "match" or "if let" is used for declaration) if let ExprKind::Loop(ref block, _, LoopSource::Loop) = expr.kind { // also check for empty `loop {}` statements + // TODO(issue #6161): Enable for no_std crates (outside of #[panic_handler]) if block.stmts.is_empty() && block.expr.is_none() && !is_no_std_crate(cx.tcx.hir().krate()) { - span_lint( + span_lint_and_help( cx, EMPTY_LOOP, expr.span, - "empty `loop {}` detected. You may want to either use `panic!()` or add \ - `std::thread::sleep(..);` to the loop body.", + "empty `loop {}` wastes CPU cycles", + None, + "You should either use `panic!()` or add `std::thread::sleep(..);` to the loop body.", ); } @@ -777,6 +821,7 @@ fn check_for_loop<'tcx>( check_for_loop_arg(cx, pat, arg, expr); check_for_loop_over_map_kv(cx, pat, arg, body, expr); check_for_mut_range_bound(cx, arg, body); + check_for_single_element_loop(cx, pat, arg, body, expr); detect_same_item_push(cx, pat, arg, body, expr); } @@ -1866,6 +1911,43 @@ fn check_for_loop_over_map_kv<'tcx>( } } +fn check_for_single_element_loop<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, +) { + if_chain! { + if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind; + if let PatKind::Binding(.., target, _) = pat.kind; + if let ExprKind::Array(ref arg_expr_list) = arg_expr.kind; + if let [arg_expression] = arg_expr_list; + if let ExprKind::Path(ref list_item) = arg_expression.kind; + if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name); + if let ExprKind::Block(ref block, _) = body.kind; + if !block.stmts.is_empty(); + + then { + let for_span = get_span_of_entire_for_loop(expr); + let mut block_str = snippet(cx, block.span, "..").into_owned(); + block_str.remove(0); + block_str.pop(); + + + span_lint_and_sugg( + cx, + SINGLE_ELEMENT_LOOP, + for_span, + "for loop over a single element", + "try", + format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str), + Applicability::MachineApplicable + ) + } + } +} + struct MutatePairDelegate<'a, 'tcx> { cx: &'a LateContext<'tcx>, hir_id_low: Option<HirId>, @@ -1969,12 +2051,11 @@ fn check_for_mutation<'tcx>( span_low: None, span_high: None, }; - let def_id = body.hir_id.owner.to_def_id(); cx.tcx.infer_ctxt().enter(|infcx| { ExprUseVisitor::new( &mut delegate, &infcx, - def_id.expect_local(), + body.hir_id.owner, cx.param_env, cx.typeck_results(), ) @@ -2920,7 +3001,14 @@ impl IterFunction { IterFunctionKind::IntoIter => String::new(), IterFunctionKind::Len => String::from(".count()"), IterFunctionKind::IsEmpty => String::from(".next().is_none()"), - IterFunctionKind::Contains(span) => format!(".any(|x| x == {})", snippet(cx, *span, "..")), + IterFunctionKind::Contains(span) => { + let s = snippet(cx, *span, ".."); + if let Some(stripped) = s.strip_prefix('&') { + format!(".any(|x| x == {})", stripped) + } else { + format!(".any(|x| x == *{})", s) + } + }, } } fn get_suggestion_text(&self) -> &'static str { diff --git a/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs index ddb8cc25077..22aa37e41fe 100644 --- a/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs +++ b/src/tools/clippy/clippy_lints/src/manual_unwrap_or.rs @@ -1,8 +1,9 @@ use crate::consts::constant_simple; use crate::utils; +use crate::utils::sugg; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::{def, Arm, Expr, ExprKind, PatKind, QPath}; +use rustc_hir::{def, Arm, Expr, ExprKind, Pat, PatKind, QPath}; use rustc_lint::LintContext; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; @@ -10,7 +11,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { /// **What it does:** - /// Finds patterns that reimplement `Option::unwrap_or`. + /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`. /// /// **Why is this bad?** /// Concise code helps focusing on behavior instead of boilerplate. @@ -33,7 +34,7 @@ declare_clippy_lint! { /// ``` pub MANUAL_UNWRAP_OR, complexity, - "finds patterns that can be encoded more concisely with `Option::unwrap_or`" + "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`" } declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]); @@ -43,32 +44,50 @@ impl LateLintPass<'_> for ManualUnwrapOr { if in_external_macro(cx.sess(), expr.span) { return; } - lint_option_unwrap_or_case(cx, expr); + lint_manual_unwrap_or(cx, expr); } } -fn lint_option_unwrap_or_case<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - fn applicable_none_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> { +#[derive(Copy, Clone)] +enum Case { + Option, + Result, +} + +impl Case { + fn unwrap_fn_path(&self) -> &str { + match self { + Case::Option => "Option::unwrap_or", + Case::Result => "Result::unwrap_or", + } + } +} + +fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + fn applicable_or_arm<'a>(arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> { if_chain! { if arms.len() == 2; if arms.iter().all(|arm| arm.guard.is_none()); - if let Some((idx, none_arm)) = arms.iter().enumerate().find(|(_, arm)| - if let PatKind::Path(ref qpath) = arm.pat.kind { - utils::match_qpath(qpath, &utils::paths::OPTION_NONE) - } else { - false + if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| + match arm.pat.kind { + PatKind::Path(ref some_qpath) => + utils::match_qpath(some_qpath, &utils::paths::OPTION_NONE), + PatKind::TupleStruct(ref err_qpath, &[Pat { kind: PatKind::Wild, .. }], _) => + utils::match_qpath(err_qpath, &utils::paths::RESULT_ERR), + _ => false, } ); - let some_arm = &arms[1 - idx]; - if let PatKind::TupleStruct(ref some_qpath, &[some_binding], _) = some_arm.pat.kind; - if utils::match_qpath(some_qpath, &utils::paths::OPTION_SOME); - if let PatKind::Binding(_, binding_hir_id, ..) = some_binding.kind; - if let ExprKind::Path(QPath::Resolved(_, body_path)) = some_arm.body.kind; + let unwrap_arm = &arms[1 - idx]; + if let PatKind::TupleStruct(ref unwrap_qpath, &[unwrap_pat], _) = unwrap_arm.pat.kind; + if utils::match_qpath(unwrap_qpath, &utils::paths::OPTION_SOME) + || utils::match_qpath(unwrap_qpath, &utils::paths::RESULT_OK); + if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind; + if let ExprKind::Path(QPath::Resolved(_, body_path)) = unwrap_arm.body.kind; if let def::Res::Local(body_path_hir_id) = body_path.res; if body_path_hir_id == binding_hir_id; - if !utils::usage::contains_return_break_continue_macro(none_arm.body); + if !utils::usage::contains_return_break_continue_macro(or_arm.body); then { - Some(none_arm) + Some(or_arm) } else { None } @@ -78,24 +97,29 @@ fn lint_option_unwrap_or_case<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tc if_chain! { if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind; let ty = cx.typeck_results().expr_ty(scrutinee); - if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)); - if let Some(none_arm) = applicable_none_arm(match_arms); - if let Some(scrutinee_snippet) = utils::snippet_opt(cx, scrutinee.span); - if let Some(none_body_snippet) = utils::snippet_opt(cx, none_arm.body.span); + if let Some(case) = if utils::is_type_diagnostic_item(cx, ty, sym!(option_type)) { + Some(Case::Option) + } else if utils::is_type_diagnostic_item(cx, ty, sym!(result_type)) { + Some(Case::Result) + } else { + None + }; + if let Some(or_arm) = applicable_or_arm(match_arms); + if let Some(or_body_snippet) = utils::snippet_opt(cx, or_arm.body.span); if let Some(indent) = utils::indent_of(cx, expr.span); - if constant_simple(cx, cx.typeck_results(), none_arm.body).is_some(); + if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some(); then { - let reindented_none_body = - utils::reindent_multiline(none_body_snippet.into(), true, Some(indent)); + let reindented_or_body = + utils::reindent_multiline(or_body_snippet.into(), true, Some(indent)); utils::span_lint_and_sugg( cx, MANUAL_UNWRAP_OR, expr.span, - "this pattern reimplements `Option::unwrap_or`", + &format!("this pattern reimplements `{}`", case.unwrap_fn_path()), "replace with", format!( "{}.unwrap_or({})", - scrutinee_snippet, - reindented_none_body, + sugg::Sugg::hir(cx, scrutinee, "..").maybe_par(), + reindented_or_body, ), Applicability::MachineApplicable, ); diff --git a/src/tools/clippy/clippy_lints/src/matches.rs b/src/tools/clippy/clippy_lints/src/matches.rs index b1a4e06d4c3..4bdfca1a292 100644 --- a/src/tools/clippy/clippy_lints/src/matches.rs +++ b/src/tools/clippy/clippy_lints/src/matches.rs @@ -1,5 +1,4 @@ use crate::consts::{constant, miri_to_const, Constant}; -use crate::utils::paths; use crate::utils::sugg::Sugg; use crate::utils::usage::is_unused; use crate::utils::{ @@ -8,8 +7,10 @@ use crate::utils::{ snippet_block, 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; use rustc_ast::ast::LitKind; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def::CtorKind; use rustc_hir::{ @@ -18,10 +19,12 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, TyS}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; +use rustc_span::Symbol; use std::cmp::Ordering; +use std::collections::hash_map::Entry; use std::collections::Bound; declare_clippy_lint! { @@ -36,7 +39,6 @@ declare_clippy_lint! { /// ```rust /// # fn bar(stool: &str) {} /// # let x = Some("abc"); - /// /// // Bad /// match x { /// Some(ref foo) => bar(foo), @@ -239,7 +241,6 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); - /// /// // Bad /// match x { /// Foo::A(_) => {}, @@ -477,6 +478,47 @@ declare_clippy_lint! { "a match that could be written with the matches! macro" } +declare_clippy_lint! { + /// **What it does:** Checks for `match` with identical arm bodies. + /// + /// **Why is this bad?** This is probably a copy & paste error. If arm bodies + /// are the same on purpose, you can factor them + /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns). + /// + /// **Known problems:** False positive possible with order dependent `match` + /// (see issue + /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)). + /// + /// **Example:** + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => bar(), // <= oops + /// } + /// ``` + /// + /// This should probably be + /// ```rust,ignore + /// match foo { + /// Bar => bar(), + /// Quz => quz(), + /// Baz => baz(), // <= fixed + /// } + /// ``` + /// + /// or if the original code was not a typo: + /// ```rust,ignore + /// match foo { + /// Bar | Baz => bar(), // <= shows the intent better + /// Quz => quz(), + /// } + /// ``` + pub MATCH_SAME_ARMS, + pedantic, + "`match` with identical arm bodies" +} + #[derive(Default)] pub struct Matches { infallible_destructuring_match_linted: bool, @@ -497,7 +539,8 @@ impl_lint_pass!(Matches => [ INFALLIBLE_DESTRUCTURING_MATCH, REST_PAT_IN_FULLY_BOUND_STRUCTS, REDUNDANT_PATTERN_MATCHING, - MATCH_LIKE_MATCHES_MACRO + MATCH_LIKE_MATCHES_MACRO, + MATCH_SAME_ARMS, ]); impl<'tcx> LateLintPass<'tcx> for Matches { @@ -507,7 +550,9 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } redundant_pattern_match::check(cx, expr); - check_match_like_matches(cx, expr); + if !check_match_like_matches(cx, expr) { + lint_match_arms(cx, expr); + } if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind { check_single_match(cx, ex, arms, expr); @@ -1065,32 +1110,47 @@ fn check_wild_in_or_pats(cx: &LateContext<'_>, arms: &[Arm<'_>]) { } /// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!` -fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { +fn check_match_like_matches<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> bool { if let ExprKind::Match(ex, arms, ref match_source) = &expr.kind { match match_source { MatchSource::Normal => find_matches_sugg(cx, ex, arms, expr, false), MatchSource::IfLetDesugar { .. } => find_matches_sugg(cx, ex, arms, expr, true), - _ => return, + _ => false, } + } else { + false } } /// Lint a `match` or desugared `if let` for replacement by `matches!` -fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, desugared: bool) { +fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>, desugared: bool) -> bool { if_chain! { - if arms.len() == 2; + if arms.len() >= 2; if cx.typeck_results().expr_ty(expr).is_bool(); - if is_wild(&arms[1].pat); - if let Some(first) = find_bool_lit(&arms[0].body.kind, desugared); - if let Some(second) = find_bool_lit(&arms[1].body.kind, desugared); - if first != second; + if let Some((b1_arm, b0_arms)) = arms.split_last(); + if let Some(b0) = find_bool_lit(&b0_arms[0].body.kind, desugared); + if let Some(b1) = find_bool_lit(&b1_arm.body.kind, desugared); + if is_wild(&b1_arm.pat); + if b0 != b1; + let if_guard = &b0_arms[0].guard; + if if_guard.is_none() || b0_arms.len() == 1; + if b0_arms[1..].iter() + .all(|arm| { + find_bool_lit(&arm.body.kind, desugared).map_or(false, |b| b == b0) && + arm.guard.is_none() + }); then { let mut applicability = Applicability::MachineApplicable; - - let pat_and_guard = if let Some(Guard::If(g)) = arms[0].guard { - format!("{} if {}", snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability), snippet_with_applicability(cx, g.span, "..", &mut applicability)) + let pat = { + use itertools::Itertools as _; + b0_arms.iter() + .map(|arm| snippet_with_applicability(cx, arm.pat.span, "..", &mut applicability)) + .join(" | ") + }; + let pat_and_guard = if let Some(Guard::If(g)) = if_guard { + format!("{} if {}", pat, snippet_with_applicability(cx, g.span, "..", &mut applicability)) } else { - format!("{}", snippet_with_applicability(cx, arms[0].pat.span, "..", &mut applicability)) + pat }; span_lint_and_sugg( cx, @@ -1100,12 +1160,15 @@ fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr "try this", format!( "{}matches!({}, {})", - if first { "" } else { "!" }, + if b0 { "" } else { "!" }, snippet_with_applicability(cx, ex.span, "..", &mut applicability), pat_and_guard, ), applicability, - ) + ); + true + } else { + false } } } @@ -1659,3 +1722,119 @@ fn test_overlapping() { ],) ); } + +/// Implementation of `MATCH_SAME_ARMS`. +fn lint_match_arms<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) { + fn same_bindings<'tcx>(lhs: &FxHashMap<Symbol, Ty<'tcx>>, rhs: &FxHashMap<Symbol, Ty<'tcx>>) -> bool { + lhs.len() == rhs.len() + && lhs + .iter() + .all(|(name, l_ty)| rhs.get(name).map_or(false, |r_ty| TyS::same_type(l_ty, r_ty))) + } + + if let ExprKind::Match(_, ref arms, MatchSource::Normal) = expr.kind { + let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(&arm.body); + h.finish() + }; + + let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool { + let min_index = usize::min(lindex, rindex); + let max_index = usize::max(lindex, rindex); + + // Arms with a guard are ignored, those can’t always be merged together + // This is also the case for arms in-between each there is an arm with a guard + (min_index..=max_index).all(|index| arms[index].guard.is_none()) && + SpanlessEq::new(cx).eq_expr(&lhs.body, &rhs.body) && + // all patterns should have the same bindings + same_bindings(&bindings(cx, &lhs.pat), &bindings(cx, &rhs.pat)) + }; + + let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect(); + for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) { + span_lint_and_then( + cx, + MATCH_SAME_ARMS, + j.body.span, + "this `match` has identical arm bodies", + |diag| { + diag.span_note(i.body.span, "same as this"); + + // Note: this does not use `span_suggestion` on purpose: + // there is no clean way + // to remove the other arm. Building a span and suggest to replace it to "" + // makes an even more confusing error message. Also in order not to make up a + // span for the whole pattern, the suggestion is only shown when there is only + // one pattern. The user should know about `|` if they are already using it… + + let lhs = snippet(cx, i.pat.span, "<pat1>"); + let rhs = snippet(cx, j.pat.span, "<pat2>"); + + if let PatKind::Wild = j.pat.kind { + // if the last arm is _, then i could be integrated into _ + // note that i.pat cannot be _, because that would mean that we're + // hiding all the subsequent arms, and rust won't compile + diag.span_note( + i.body.span, + &format!( + "`{}` has the same arm body as the `_` wildcard, consider removing it", + lhs + ), + ); + } else { + diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs)); + } + }, + ); + } + } +} + +/// Returns the list of bindings in a pattern. +fn bindings<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>) -> FxHashMap<Symbol, Ty<'tcx>> { + fn bindings_impl<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, map: &mut FxHashMap<Symbol, Ty<'tcx>>) { + match pat.kind { + PatKind::Box(ref pat) | PatKind::Ref(ref pat, _) => bindings_impl(cx, pat, map), + PatKind::TupleStruct(_, pats, _) => { + for pat in pats { + bindings_impl(cx, pat, map); + } + }, + PatKind::Binding(.., ident, ref as_pat) => { + if let Entry::Vacant(v) = map.entry(ident.name) { + v.insert(cx.typeck_results().pat_ty(pat)); + } + if let Some(ref as_pat) = *as_pat { + bindings_impl(cx, as_pat, map); + } + }, + PatKind::Or(fields) | PatKind::Tuple(fields, _) => { + for pat in fields { + bindings_impl(cx, pat, map); + } + }, + PatKind::Struct(_, fields, _) => { + for pat in fields { + bindings_impl(cx, &pat.pat, map); + } + }, + PatKind::Slice(lhs, ref mid, rhs) => { + for pat in lhs { + bindings_impl(cx, pat, map); + } + if let Some(ref mid) = *mid { + bindings_impl(cx, mid, map); + } + for pat in rhs { + bindings_impl(cx, pat, map); + } + }, + PatKind::Lit(..) | PatKind::Range(..) | PatKind::Wild | PatKind::Path(..) => (), + } + } + + let mut result = FxHashMap::default(); + bindings_impl(cx, pat, &mut result); + result +} diff --git a/src/tools/clippy/clippy_lints/src/misc_early.rs b/src/tools/clippy/clippy_lints/src/misc_early.rs index 9cb1cfb915d..5bc45c87874 100644 --- a/src/tools/clippy/clippy_lints/src/misc_early.rs +++ b/src/tools/clippy/clippy_lints/src/misc_early.rs @@ -231,7 +231,6 @@ declare_clippy_lint! { /// ```rust /// # struct TupleStruct(u32, u32, u32); /// # let t = TupleStruct(1, 2, 3); - /// /// // Bad /// match t { /// TupleStruct(0, .., _) => (), diff --git a/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs b/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs new file mode 100644 index 00000000000..df1cecb328c --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/mut_mutex_lock.rs @@ -0,0 +1,68 @@ +use crate::utils::{is_type_diagnostic_item, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `&mut Mutex::lock` calls + /// + /// **Why is this bad?** `Mutex::lock` is less efficient than + /// calling `Mutex::get_mut`. In addition you also have a statically + /// guarantee that the mutex isn't locked, instead of just a runtime + /// guarantee. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let mut value = value_mutex.lock().unwrap(); + /// *value += 1; + /// ``` + /// Use instead: + /// ```rust + /// use std::sync::{Arc, Mutex}; + /// + /// let mut value_rc = Arc::new(Mutex::new(42_u8)); + /// let value_mutex = Arc::get_mut(&mut value_rc).unwrap(); + /// + /// let value = value_mutex.get_mut().unwrap(); + /// *value += 1; + /// ``` + pub MUT_MUTEX_LOCK, + style, + "`&mut Mutex::lock` does unnecessary locking" +} + +declare_lint_pass!(MutMutexLock => [MUT_MUTEX_LOCK]); + +impl<'tcx> LateLintPass<'tcx> for MutMutexLock { + fn check_expr(&mut self, cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>) { + if_chain! { + if let ExprKind::MethodCall(path, method_span, args, _) = &ex.kind; + if path.ident.name == sym!(lock); + let ty = cx.typeck_results().expr_ty(&args[0]); + if let ty::Ref(_, inner_ty, Mutability::Mut) = ty.kind(); + if is_type_diagnostic_item(cx, inner_ty, sym!(mutex_type)); + then { + span_lint_and_sugg( + cx, + MUT_MUTEX_LOCK, + *method_span, + "calling `&mut Mutex::lock` unnecessarily locks an exclusive (mutable) reference", + "change this to", + "get_mut".to_owned(), + Applicability::MaybeIncorrect, + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs index dc5aa669139..a799a644e97 100644 --- a/src/tools/clippy/clippy_lints/src/needless_bool.rs +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -3,7 +3,9 @@ //! This lint is **warn** by default use crate::utils::sugg::Sugg; -use crate::utils::{higher, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg}; +use crate::utils::{ + higher, is_expn_of, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg, +}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -233,6 +235,9 @@ fn check_comparison<'a, 'tcx>( cx.typeck_results().expr_ty(left_side), cx.typeck_results().expr_ty(right_side), ); + if is_expn_of(left_side.span, "cfg").is_some() || is_expn_of(right_side.span, "cfg").is_some() { + return; + } if l_ty.is_bool() && r_ty.is_bool() { let mut applicability = Applicability::MachineApplicable; @@ -295,7 +300,14 @@ fn suggest_bool_comparison<'a, 'tcx>( message: &str, conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, ) { - let hint = Sugg::hir_with_applicability(cx, expr, "..", &mut applicability); + let hint = if expr.span.from_expansion() { + if applicability != Applicability::Unspecified { + applicability = Applicability::MaybeIncorrect; + } + Sugg::hir_with_macro_callsite(cx, expr, "..") + } else { + Sugg::hir_with_applicability(cx, expr, "..", &mut applicability) + }; span_lint_and_sugg( cx, BOOL_COMPARISON, diff --git a/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs new file mode 100644 index 00000000000..28816c3076d --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/pass_by_ref_or_value.rs @@ -0,0 +1,256 @@ +use std::cmp; + +use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg}; +use if_chain::if_chain; +use rustc_ast::attr; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, ItemKind, MutTy, Mutability, Node, PatKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::Span; +use rustc_target::abi::LayoutOf; +use rustc_target::spec::abi::Abi; +use rustc_target::spec::Target; + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by reference, where + /// the argument type is `Copy` and small enough to be more efficient to always + /// pass by value. + /// + /// **Why is this bad?** In many calling conventions instances of structs will + /// be passed through registers if they fit into two or less general purpose + /// registers. + /// + /// **Known problems:** This lint is target register size dependent, it is + /// limited to 32-bit to try and reduce portability problems between 32 and + /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit + /// will be different. + /// + /// The configuration option `trivial_copy_size_limit` can be set to override + /// this limit for a project. + /// + /// This lint attempts to allow passing arguments by reference if a reference + /// to that argument is returned. This is implemented by comparing the lifetime + /// of the argument and return value for equality. However, this can cause + /// false positives in cases involving multiple lifetimes that are bounded by + /// each other. + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// fn foo(v: &u32) {} + /// ``` + /// + /// ```rust + /// // Better + /// fn foo(v: u32) {} + /// ``` + pub TRIVIALLY_COPY_PASS_BY_REF, + pedantic, + "functions taking small copyable arguments by reference" +} + +declare_clippy_lint! { + /// **What it does:** Checks for functions taking arguments by value, where + /// the argument type is `Copy` and large enough to be worth considering + /// passing by reference. Does not trigger if the function is being exported, + /// because that might induce API breakage, if the parameter is declared as mutable, + /// or if the argument is a `self`. + /// + /// **Why is this bad?** Arguments passed by value might result in an unnecessary + /// shallow copy, taking up more space in the stack and requiring a call to + /// `memcpy`, which which can be expensive. + /// + /// **Example:** + /// + /// ```rust + /// #[derive(Clone, Copy)] + /// struct TooLarge([u8; 2048]); + /// + /// // Bad + /// fn foo(v: TooLarge) {} + /// ``` + /// ```rust + /// #[derive(Clone, Copy)] + /// struct TooLarge([u8; 2048]); + /// + /// // Good + /// fn foo(v: &TooLarge) {} + /// ``` + pub LARGE_TYPES_PASSED_BY_VALUE, + pedantic, + "functions taking large arguments by value" +} + +#[derive(Copy, Clone)] +pub struct PassByRefOrValue { + ref_min_size: u64, + value_max_size: u64, +} + +impl<'tcx> PassByRefOrValue { + pub fn new(ref_min_size: Option<u64>, value_max_size: u64, target: &Target) -> Self { + let ref_min_size = ref_min_size.unwrap_or_else(|| { + let bit_width = u64::from(target.pointer_width); + // Cap the calculated bit width at 32-bits to reduce + // portability problems between 32 and 64-bit targets + let bit_width = cmp::min(bit_width, 32); + #[allow(clippy::integer_division)] + let byte_width = bit_width / 8; + // Use a limit of 2 times the register byte width + byte_width * 2 + }); + + Self { + ref_min_size, + value_max_size, + } + } + + fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option<Span>) { + let fn_def_id = cx.tcx.hir().local_def_id(hir_id); + + let fn_sig = cx.tcx.fn_sig(fn_def_id); + let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig); + + let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id)); + + for (index, (input, &ty)) in decl.inputs.iter().zip(fn_sig.inputs()).enumerate() { + // All spans generated from a proc-macro invocation are the same... + match span { + Some(s) if s == input.span => return, + _ => (), + } + + match ty.kind() { + ty::Ref(input_lt, ty, Mutability::Not) => { + // Use lifetimes to determine if we're returning a reference to the + // argument. In that case we can't switch to pass-by-value as the + // argument will not live long enough. + let output_lts = match *fn_sig.output().kind() { + ty::Ref(output_lt, _, _) => vec![output_lt], + ty::Adt(_, substs) => substs.regions().collect(), + _ => vec![], + }; + + if_chain! { + if !output_lts.contains(&input_lt); + if is_copy(cx, ty); + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if size <= self.ref_min_size; + if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind; + then { + let value_type = if is_self_ty(decl_ty) { + "self".into() + } else { + snippet(cx, decl_ty.span, "_").into() + }; + span_lint_and_sugg( + cx, + TRIVIALLY_COPY_PASS_BY_REF, + input.span, + &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size), + "consider passing by value instead", + value_type, + Applicability::Unspecified, + ); + } + } + }, + + ty::Adt(_, _) | ty::Array(_, _) | ty::Tuple(_) => { + // if function has a body and parameter is annotated with mut, ignore + if let Some(param) = fn_body.and_then(|body| body.params.get(index)) { + match param.pat.kind { + PatKind::Binding(BindingAnnotation::Unannotated, _, _, _) => {}, + _ => continue, + } + } + + if_chain! { + if !cx.access_levels.is_exported(hir_id); + if is_copy(cx, ty); + if !is_self_ty(input); + if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); + if size > self.value_max_size; + then { + span_lint_and_sugg( + cx, + LARGE_TYPES_PASSED_BY_VALUE, + input.span, + &format!("this argument ({} byte) is passed by value, but might be more efficient if passed by reference (limit: {} byte)", size, self.value_max_size), + "consider passing by reference instead", + format!("&{}", snippet(cx, input.span, "_")), + Applicability::MaybeIncorrect, + ); + } + } + }, + + _ => {}, + } + } + } +} + +impl_lint_pass!(PassByRefOrValue => [TRIVIALLY_COPY_PASS_BY_REF, LARGE_TYPES_PASSED_BY_VALUE]); + +impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { + if item.span.from_expansion() { + return; + } + + if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind { + self.check_poly_fn(cx, item.hir_id, &*method_sig.decl, None); + } + } + + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + _body: &'tcx Body<'_>, + span: Span, + hir_id: HirId, + ) { + if span.from_expansion() { + return; + } + + match kind { + FnKind::ItemFn(.., header, _, attrs) => { + if header.abi != Abi::Rust { + return; + } + for a in attrs { + if let Some(meta_items) = a.meta_item_list() { + if a.has_name(sym!(proc_macro_derive)) + || (a.has_name(sym!(inline)) && attr::list_contains_name(&meta_items, sym!(always))) + { + return; + } + } + } + }, + FnKind::Method(..) => (), + FnKind::Closure(..) => return, + } + + // Exclude non-inherent impls + if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { + if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. } | + ItemKind::Trait(..)) + { + return; + } + } + + self.check_poly_fn(cx, hir_id, decl, Some(span)); + } +} diff --git a/src/tools/clippy/clippy_lints/src/ranges.rs b/src/tools/clippy/clippy_lints/src/ranges.rs index cc492917b9d..de54711d851 100644 --- a/src/tools/clippy/clippy_lints/src/ranges.rs +++ b/src/tools/clippy/clippy_lints/src/ranges.rs @@ -2,15 +2,19 @@ use crate::consts::{constant, Constant}; use if_chain::if_chain; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Spanned; +use rustc_span::source_map::{Span, Spanned}; +use rustc_span::symbol::Ident; use std::cmp::Ordering; use crate::utils::sugg::Sugg; -use crate::utils::{get_parent_expr, is_integer_const, snippet, snippet_opt, span_lint, span_lint_and_then}; +use crate::utils::{ + get_parent_expr, is_integer_const, single_segment_path, snippet, snippet_opt, snippet_with_applicability, + span_lint, span_lint_and_sugg, span_lint_and_then, +}; use crate::utils::{higher, SpanlessEq}; declare_clippy_lint! { @@ -128,43 +132,51 @@ declare_clippy_lint! { "reversing the limits of range expressions, resulting in empty ranges" } +declare_clippy_lint! { + /// **What it does:** Checks for expressions like `x >= 3 && x < 8` that could + /// be more readably expressed as `(3..8).contains(x)`. + /// + /// **Why is this bad?** `contains` expresses the intent better and has less + /// failure modes (such as fencepost errors or using `||` instead of `&&`). + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```rust + /// // given + /// let x = 6; + /// + /// assert!(x >= 3 && x < 8); + /// ``` + /// Use instead: + /// ```rust + ///# let x = 6; + /// assert!((3..8).contains(&x)); + /// ``` + pub MANUAL_RANGE_CONTAINS, + style, + "manually reimplementing {`Range`, `RangeInclusive`}`::contains`" +} + declare_lint_pass!(Ranges => [ RANGE_ZIP_WITH_LEN, RANGE_PLUS_ONE, RANGE_MINUS_ONE, REVERSED_EMPTY_RANGES, + MANUAL_RANGE_CONTAINS, ]); impl<'tcx> LateLintPass<'tcx> for Ranges { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::MethodCall(ref path, _, ref args, _) = expr.kind { - let name = path.ident.as_str(); - if name == "zip" && args.len() == 2 { - let iter = &args[0].kind; - let zip_arg = &args[1]; - if_chain! { - // `.iter()` call - if let ExprKind::MethodCall(ref iter_path, _, ref iter_args , _) = *iter; - if iter_path.ident.name == sym!(iter); - // range expression in `.zip()` call: `0..x.len()` - if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(zip_arg); - if is_integer_const(cx, start, 0); - // `.len()` call - if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind; - if len_path.ident.name == sym!(len) && len_args.len() == 1; - // `.iter()` and `.len()` called on same `Path` - if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind; - if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind; - if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); - then { - span_lint(cx, - RANGE_ZIP_WITH_LEN, - expr.span, - &format!("it is more idiomatic to use `{}.iter().enumerate()`", - snippet(cx, iter_args[0].span, "_"))); - } - } - } + match expr.kind { + ExprKind::MethodCall(ref path, _, ref args, _) => { + check_range_zip_with_len(cx, path, args, expr.span); + }, + ExprKind::Binary(ref op, ref l, ref r) => { + check_possible_range_contains(cx, op.node, l, r, expr.span); + }, + _ => {}, } check_exclusive_range_plus_one(cx, expr); @@ -173,6 +185,148 @@ impl<'tcx> LateLintPass<'tcx> for Ranges { } } +fn check_possible_range_contains(cx: &LateContext<'_>, op: BinOpKind, l: &Expr<'_>, r: &Expr<'_>, span: Span) { + let combine_and = match op { + BinOpKind::And | BinOpKind::BitAnd => true, + BinOpKind::Or | BinOpKind::BitOr => false, + _ => return, + }; + // value, name, order (higher/lower), inclusiveness + if let (Some((lval, lname, name_span, lval_span, lord, linc)), Some((rval, rname, _, rval_span, rord, rinc))) = + (check_range_bounds(cx, l), check_range_bounds(cx, r)) + { + // we only lint comparisons on the same name and with different + // direction + if lname != rname || lord == rord { + return; + } + let ord = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(l), &lval, &rval); + if combine_and && ord == Some(rord) { + // order lower bound and upper bound + let (l_span, u_span, l_inc, u_inc) = if rord == Ordering::Less { + (lval_span, rval_span, linc, rinc) + } else { + (rval_span, lval_span, rinc, linc) + }; + // we only lint inclusive lower bounds + if !l_inc { + return; + } + let (range_type, range_op) = if u_inc { + ("RangeInclusive", "..=") + } else { + ("Range", "..") + }; + let mut applicability = Applicability::MachineApplicable; + let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); + let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); + span_lint_and_sugg( + cx, + MANUAL_RANGE_CONTAINS, + span, + &format!("manual `{}::contains` implementation", range_type), + "use", + format!("({}{}{}).contains(&{})", lo, range_op, hi, name), + applicability, + ); + } else if !combine_and && ord == Some(lord) { + // `!_.contains(_)` + // order lower bound and upper bound + let (l_span, u_span, l_inc, u_inc) = if lord == Ordering::Less { + (lval_span, rval_span, linc, rinc) + } else { + (rval_span, lval_span, rinc, linc) + }; + if l_inc { + return; + } + let (range_type, range_op) = if u_inc { + ("Range", "..") + } else { + ("RangeInclusive", "..=") + }; + let mut applicability = Applicability::MachineApplicable; + let name = snippet_with_applicability(cx, name_span, "_", &mut applicability); + let lo = snippet_with_applicability(cx, l_span, "_", &mut applicability); + let hi = snippet_with_applicability(cx, u_span, "_", &mut applicability); + span_lint_and_sugg( + cx, + MANUAL_RANGE_CONTAINS, + span, + &format!("manual `!{}::contains` implementation", range_type), + "use", + format!("!({}{}{}).contains(&{})", lo, range_op, hi, name), + applicability, + ); + } + } +} + +fn check_range_bounds(cx: &LateContext<'_>, ex: &Expr<'_>) -> Option<(Constant, Ident, Span, Span, Ordering, bool)> { + if let ExprKind::Binary(ref op, ref l, ref r) = ex.kind { + let (inclusive, ordering) = match op.node { + BinOpKind::Gt => (false, Ordering::Greater), + BinOpKind::Ge => (true, Ordering::Greater), + BinOpKind::Lt => (false, Ordering::Less), + BinOpKind::Le => (true, Ordering::Less), + _ => return None, + }; + if let Some(id) = match_ident(l) { + if let Some((c, _)) = constant(cx, cx.typeck_results(), r) { + return Some((c, id, l.span, r.span, ordering, inclusive)); + } + } else if let Some(id) = match_ident(r) { + if let Some((c, _)) = constant(cx, cx.typeck_results(), l) { + return Some((c, id, r.span, l.span, ordering.reverse(), inclusive)); + } + } + } + None +} + +fn match_ident(e: &Expr<'_>) -> Option<Ident> { + if let ExprKind::Path(ref qpath) = e.kind { + if let Some(seg) = single_segment_path(qpath) { + if seg.args.is_none() { + return Some(seg.ident); + } + } + } + None +} + +fn check_range_zip_with_len(cx: &LateContext<'_>, path: &PathSegment<'_>, args: &[Expr<'_>], span: Span) { + let name = path.ident.as_str(); + if name == "zip" && args.len() == 2 { + let iter = &args[0].kind; + let zip_arg = &args[1]; + if_chain! { + // `.iter()` call + if let ExprKind::MethodCall(ref iter_path, _, ref iter_args, _) = *iter; + if iter_path.ident.name == sym!(iter); + // range expression in `.zip()` call: `0..x.len()` + if let Some(higher::Range { start: Some(start), end: Some(end), .. }) = higher::range(zip_arg); + if is_integer_const(cx, start, 0); + // `.len()` call + if let ExprKind::MethodCall(ref len_path, _, ref len_args, _) = end.kind; + if len_path.ident.name == sym!(len) && len_args.len() == 1; + // `.iter()` and `.len()` called on same `Path` + if let ExprKind::Path(QPath::Resolved(_, ref iter_path)) = iter_args[0].kind; + if let ExprKind::Path(QPath::Resolved(_, ref len_path)) = len_args[0].kind; + if SpanlessEq::new(cx).eq_path_segments(&iter_path.segments, &len_path.segments); + then { + span_lint(cx, + RANGE_ZIP_WITH_LEN, + span, + &format!("it is more idiomatic to use `{}.iter().enumerate()`", + snippet(cx, iter_args[0].span, "_")) + ); + } + } + } +} + // exclusive range plus one: `x..(y+1)` fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { if_chain! { diff --git a/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs b/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs deleted file mode 100644 index e90ea0fc200..00000000000 --- a/src/tools/clippy/clippy_lints/src/trivially_copy_pass_by_ref.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::cmp; - -use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg}; -use if_chain::if_chain; -use rustc_ast::attr; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl, HirId, ItemKind, MutTy, Mutability, Node}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::Span; -use rustc_target::abi::LayoutOf; -use rustc_target::spec::abi::Abi; -use rustc_target::spec::Target; - -declare_clippy_lint! { - /// **What it does:** Checks for functions taking arguments by reference, where - /// the argument type is `Copy` and small enough to be more efficient to always - /// pass by value. - /// - /// **Why is this bad?** In many calling conventions instances of structs will - /// be passed through registers if they fit into two or less general purpose - /// registers. - /// - /// **Known problems:** This lint is target register size dependent, it is - /// limited to 32-bit to try and reduce portability problems between 32 and - /// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit - /// will be different. - /// - /// The configuration option `trivial_copy_size_limit` can be set to override - /// this limit for a project. - /// - /// This lint attempts to allow passing arguments by reference if a reference - /// to that argument is returned. This is implemented by comparing the lifetime - /// of the argument and return value for equality. However, this can cause - /// false positives in cases involving multiple lifetimes that are bounded by - /// each other. - /// - /// **Example:** - /// - /// ```rust - /// // Bad - /// fn foo(v: &u32) {} - /// ``` - /// - /// ```rust - /// // Better - /// fn foo(v: u32) {} - /// ``` - pub TRIVIALLY_COPY_PASS_BY_REF, - pedantic, - "functions taking small copyable arguments by reference" -} - -#[derive(Copy, Clone)] -pub struct TriviallyCopyPassByRef { - limit: u64, -} - -impl<'tcx> TriviallyCopyPassByRef { - pub fn new(limit: Option<u64>, target: &Target) -> Self { - let limit = limit.unwrap_or_else(|| { - let bit_width = u64::from(target.pointer_width); - // Cap the calculated bit width at 32-bits to reduce - // portability problems between 32 and 64-bit targets - let bit_width = cmp::min(bit_width, 32); - #[allow(clippy::integer_division)] - let byte_width = bit_width / 8; - // Use a limit of 2 times the register byte width - byte_width * 2 - }); - Self { limit } - } - - fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, hir_id: HirId, decl: &FnDecl<'_>, span: Option<Span>) { - let fn_def_id = cx.tcx.hir().local_def_id(hir_id); - - let fn_sig = cx.tcx.fn_sig(fn_def_id); - let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig); - - // Use lifetimes to determine if we're returning a reference to the - // argument. In that case we can't switch to pass-by-value as the - // argument will not live long enough. - let output_lts = match *fn_sig.output().kind() { - ty::Ref(output_lt, _, _) => vec![output_lt], - ty::Adt(_, substs) => substs.regions().collect(), - _ => vec![], - }; - - for (input, &ty) in decl.inputs.iter().zip(fn_sig.inputs()) { - // All spans generated from a proc-macro invocation are the same... - match span { - Some(s) if s == input.span => return, - _ => (), - } - - if_chain! { - if let ty::Ref(input_lt, ty, Mutability::Not) = ty.kind(); - if !output_lts.contains(&input_lt); - if is_copy(cx, ty); - if let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes()); - if size <= self.limit; - if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind; - then { - let value_type = if is_self_ty(decl_ty) { - "self".into() - } else { - snippet(cx, decl_ty.span, "_").into() - }; - span_lint_and_sugg( - cx, - TRIVIALLY_COPY_PASS_BY_REF, - input.span, - &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.limit), - "consider passing by value instead", - value_type, - Applicability::Unspecified, - ); - } - } - } - } -} - -impl_lint_pass!(TriviallyCopyPassByRef => [TRIVIALLY_COPY_PASS_BY_REF]); - -impl<'tcx> LateLintPass<'tcx> for TriviallyCopyPassByRef { - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { - if item.span.from_expansion() { - return; - } - - if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind { - self.check_poly_fn(cx, item.hir_id, &*method_sig.decl, None); - } - } - - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: FnKind<'tcx>, - decl: &'tcx FnDecl<'_>, - _body: &'tcx Body<'_>, - span: Span, - hir_id: HirId, - ) { - if span.from_expansion() { - return; - } - - match kind { - FnKind::ItemFn(.., header, _, attrs) => { - if header.abi != Abi::Rust { - return; - } - for a in attrs { - if let Some(meta_items) = a.meta_item_list() { - if a.has_name(sym!(proc_macro_derive)) - || (a.has_name(sym!(inline)) && attr::list_contains_name(&meta_items, sym!(always))) - { - return; - } - } - } - }, - FnKind::Method(..) => (), - FnKind::Closure(..) => return, - } - - // Exclude non-inherent impls - if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { - if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), .. } | - ItemKind::Trait(..)) - { - return; - } - } - - self.check_poly_fn(cx, hir_id, decl, Some(span)); - } -} diff --git a/src/tools/clippy/clippy_lints/src/types.rs b/src/tools/clippy/clippy_lints/src/types.rs index 9a948af8bfc..6a33aaaaab2 100644 --- a/src/tools/clippy/clippy_lints/src/types.rs +++ b/src/tools/clippy/clippy_lints/src/types.rs @@ -11,7 +11,7 @@ use rustc_hir as hir; use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::{ BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem, - ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, Node, QPath, Stmt, StmtKind, + ImplItemKind, Item, ItemKind, Lifetime, Lit, Local, MatchSource, MutTy, Mutability, Node, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind, TyKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -1224,7 +1224,8 @@ declare_clippy_lint! { } declare_clippy_lint! { - /// **What it does:** Checks for casts to the same type. + /// **What it does:** Checks for casts to the same type, casts of int literals to integer types + /// and casts of float literals to float types. /// /// **Why is this bad?** It's just unnecessary. /// @@ -1233,6 +1234,14 @@ declare_clippy_lint! { /// **Example:** /// ```rust /// let _ = 2i32 as i32; + /// let _ = 0.5 as f32; + /// ``` + /// + /// Better: + /// + /// ```rust + /// let _ = 2_i32; + /// let _ = 0.5_f32; /// ``` pub UNNECESSARY_CAST, complexity, @@ -1598,7 +1607,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if let ExprKind::Cast(ref ex, _) = expr.kind { let (cast_from, cast_to) = (cx.typeck_results().expr_ty(ex), cx.typeck_results().expr_ty(expr)); lint_fn_to_numeric_cast(cx, expr, ex, cast_from, cast_to); - if let ExprKind::Lit(ref lit) = ex.kind { + if let Some(lit) = get_numeric_literal(ex) { + let literal_str = snippet_opt(cx, ex.span).unwrap_or_default(); + if_chain! { if let LitKind::Int(n, _) = lit.node; if let Some(src) = snippet_opt(cx, lit.span); @@ -1608,19 +1619,19 @@ impl<'tcx> LateLintPass<'tcx> for Casts { let to_nbits = fp_ty_mantissa_nbits(cast_to); if from_nbits != 0 && to_nbits != 0 && from_nbits <= to_nbits && num_lit.is_decimal(); then { - span_lint_and_sugg( - cx, - UNNECESSARY_CAST, - expr.span, - &format!("casting integer literal to `{}` is unnecessary", cast_to), - "try", - format!("{}_{}", n, cast_to), - Applicability::MachineApplicable, - ); + let literal_str = if is_unary_neg(ex) { format!("-{}", num_lit.integer) } else { num_lit.integer.into() }; + show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); return; } } + match lit.node { + LitKind::Int(_, LitIntType::Unsuffixed) if cast_to.is_integral() => { + show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, + LitKind::Float(_, LitFloatType::Unsuffixed) if cast_to.is_floating_point() => { + show_unnecessary_cast(cx, expr, &literal_str, cast_from, cast_to); + }, LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed) => {}, _ => { if cast_from.kind() == cast_to.kind() && !in_external_macro(cx.sess(), expr.span) { @@ -1646,6 +1657,37 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } } +fn is_unary_neg(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Unary(UnOp::UnNeg, _)) +} + +fn get_numeric_literal<'e>(expr: &'e Expr<'e>) -> Option<&'e Lit> { + match expr.kind { + ExprKind::Lit(ref lit) => Some(lit), + ExprKind::Unary(UnOp::UnNeg, e) => { + if let ExprKind::Lit(ref lit) = e.kind { + Some(lit) + } else { + None + } + }, + _ => None, + } +} + +fn show_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &str, cast_from: Ty<'_>, cast_to: Ty<'_>) { + let literal_kind_name = if cast_from.is_integral() { "integer" } else { "float" }; + span_lint_and_sugg( + cx, + UNNECESSARY_CAST, + expr.span, + &format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to), + "try", + format!("{}_{}", literal_str, cast_to), + Applicability::MachineApplicable, + ); +} + fn lint_numeric_casts<'tcx>( cx: &LateContext<'tcx>, expr: &Expr<'tcx>, diff --git a/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs b/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs new file mode 100644 index 00000000000..5443f1601fc --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/undropped_manually_drops.rs @@ -0,0 +1,50 @@ +use crate::utils::{is_type_lang_item, match_function_call, paths, span_lint_and_help}; +use rustc_hir::{lang_items, Expr}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Prevents the safe `std::mem::drop` function from being called on `std::mem::ManuallyDrop`. + /// + /// **Why is this bad?** The safe `drop` function does not drop the inner value of a `ManuallyDrop`. + /// + /// **Known problems:** Does not catch cases if the user binds `std::mem::drop` + /// to a different name and calls it that way. + /// + /// **Example:** + /// + /// ```rust + /// struct S; + /// drop(std::mem::ManuallyDrop::new(S)); + /// ``` + /// Use instead: + /// ```rust + /// struct S; + /// unsafe { + /// std::mem::ManuallyDrop::drop(&mut std::mem::ManuallyDrop::new(S)); + /// } + /// ``` + pub UNDROPPED_MANUALLY_DROPS, + correctness, + "use of safe `std::mem::drop` function to drop a std::mem::ManuallyDrop, which will not drop the inner value" +} + +declare_lint_pass!(UndroppedManuallyDrops => [UNDROPPED_MANUALLY_DROPS]); + +impl LateLintPass<'tcx> for UndroppedManuallyDrops { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(ref args) = match_function_call(cx, expr, &paths::DROP) { + let ty = cx.typeck_results().expr_ty(&args[0]); + if is_type_lang_item(cx, ty, lang_items::LangItem::ManuallyDrop) { + span_lint_and_help( + cx, + UNDROPPED_MANUALLY_DROPS, + expr.span, + "the inner value of this ManuallyDrop will not be dropped", + None, + "to drop a `ManuallyDrop<T>`, use std::mem::ManuallyDrop::drop", + ); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs index dd2fd0bb445..0ac8fff69f0 100644 --- a/src/tools/clippy/clippy_lints/src/utils/conf.rs +++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs @@ -150,6 +150,8 @@ define_Conf! { (literal_representation_threshold, "literal_representation_threshold": u64, 16384), /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. (trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None), + /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value. + (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256), /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have (too_many_lines_threshold, "too_many_lines_threshold": u64, 100), /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack 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 c9e639e8728..e4ad105c351 100644 --- a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs +++ b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs @@ -261,14 +261,8 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool { // The == of idents doesn't work with different contexts, // we have to be explicit about hygiene - if left.ident.as_str() != right.ident.as_str() { - return false; - } - match (&left.args, &right.args) { - (&None, &None) => true, - (&Some(ref l), &Some(ref r)) => self.eq_path_parameters(l, r), - _ => false, - } + left.ident.as_str() == right.ident.as_str() + && both(&left.args, &right.args, |l, r| self.eq_path_parameters(l, r)) } pub fn eq_ty(&mut self, left: &Ty<'_>, right: &Ty<'_>) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs index bfe426a25eb..6ca72d895c8 100644 --- a/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs +++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints.rs @@ -1,3 +1,4 @@ +use crate::consts::{constant_simple, Constant}; use crate::utils::{ is_expn_of, match_def_path, match_qpath, match_type, method_calls, path_to_res, paths, qpath_res, run_lints, snippet, span_lint, span_lint_and_help, span_lint_and_sugg, SpanlessEq, @@ -14,9 +15,11 @@ use rustc_hir::intravisit::{NestedVisitorMap, Visitor}; use rustc_hir::{Crate, Expr, ExprKind, HirId, Item, MutTy, Mutability, Node, Path, StmtKind, Ty, TyKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_middle::hir::map::Map; +use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass}; use rustc_span::source_map::{Span, Spanned}; use rustc_span::symbol::{Symbol, SymbolStr}; +use rustc_typeck::hir_ty_to_ty; use std::borrow::{Borrow, Cow}; @@ -229,6 +232,21 @@ declare_clippy_lint! { "using `utils::match_type()` instead of `utils::is_type_diagnostic_item()`" } +declare_clippy_lint! { + /// **What it does:** + /// Checks the paths module for invalid paths. + /// + /// **Why is this bad?** + /// It indicates a bug in the code. + /// + /// **Known problems:** None. + /// + /// **Example:** None. + pub INVALID_PATHS, + internal, + "invalid path" +} + declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]); impl EarlyLintPass for ClippyLintsInternal { @@ -761,3 +779,64 @@ fn path_to_matched_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Ve None } + +// This is not a complete resolver for paths. It works on all the paths currently used in the paths +// module. That's all it does and all it needs to do. +pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { + if path_to_res(cx, path).is_some() { + return true; + } + + // Some implementations can't be found by `path_to_res`, particularly inherent + // implementations of native types. Check lang items. + let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect(); + let lang_items = cx.tcx.lang_items(); + for lang_item in lang_items.items() { + if let Some(def_id) = lang_item { + let lang_item_path = cx.get_def_path(*def_id); + if path_syms.starts_with(&lang_item_path) { + if let [item] = &path_syms[lang_item_path.len()..] { + for child in cx.tcx.item_children(*def_id) { + if child.ident.name == *item { + return true; + } + } + } + } + } + } + + false +} + +declare_lint_pass!(InvalidPaths => [INVALID_PATHS]); + +impl<'tcx> LateLintPass<'tcx> for InvalidPaths { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { + let local_def_id = &cx.tcx.parent_module(item.hir_id); + 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; + 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(); + if el_ty.is_str(); + let body = cx.tcx.hir().body(body_id); + let typeck_results = cx.tcx.typeck_body(body_id); + if let Some(Constant::Vec(path)) = constant_simple(cx, typeck_results, &body.value); + let path: Vec<&str> = path.iter().map(|x| { + if let Constant::Str(s) = x { + s.as_str() + } else { + // We checked the type of the constant above + unreachable!() + } + }).collect(); + if !check_path(cx, &path[..]); + then { + span_lint(cx, CLIPPY_LINTS_INTERNAL, item.span, "invalid path"); + } + } + } +} diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs index a9d26d48b12..0a8a4a5f9ae 100644 --- a/src/tools/clippy/clippy_lints/src/utils/mod.rs +++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs @@ -27,11 +27,14 @@ pub use self::diagnostics::*; 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}; use rustc_attr as attr; +use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -268,6 +271,7 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> { 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(); @@ -277,6 +281,12 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> { 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 { @@ -284,10 +294,28 @@ pub fn path_to_res(cx: &LateContext<'_>, path: &[&str]) -> Option<def::Res> { return Some(item.res); } + current_item = Some(item); items = cx.tcx.item_children(item.res.def_id()); break; } } + + // 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); + } + } } } else { None @@ -299,7 +327,7 @@ pub fn qpath_res(cx: &LateContext<'_>, qpath: &hir::QPath<'_>, id: hir::HirId) - hir::QPath::Resolved(_, path) => path.res, hir::QPath::TypeRelative(..) | hir::QPath::LangItem(..) => { if cx.tcx.has_typeck_results(id.owner.to_def_id()) { - cx.tcx.typeck(id.owner.to_def_id().expect_local()).qpath_res(qpath, id) + cx.tcx.typeck(id.owner).qpath_res(qpath, id) } else { Res::Err } @@ -1440,6 +1468,41 @@ pub fn is_slice_of_primitives(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<S None } +/// returns list of all pairs (a, b) from `exprs` such that `eq(a, b)` +/// `hash` must be comformed with `eq` +pub fn search_same<T, Hash, Eq>(exprs: &[T], hash: Hash, eq: Eq) -> Vec<(&T, &T)> +where + Hash: Fn(&T) -> u64, + Eq: Fn(&T, &T) -> bool, +{ + if exprs.len() == 2 && eq(&exprs[0], &exprs[1]) { + return vec![(&exprs[0], &exprs[1])]; + } + + let mut match_expr_list: Vec<(&T, &T)> = Vec::new(); + + let mut map: FxHashMap<_, Vec<&_>> = + FxHashMap::with_capacity_and_hasher(exprs.len(), BuildHasherDefault::default()); + + for expr in exprs { + match map.entry(hash(expr)) { + Entry::Occupied(mut o) => { + for o in o.get() { + if eq(o, expr) { + match_expr_list.push((o, expr)); + } + } + o.get_mut().push(expr); + }, + Entry::Vacant(v) => { + v.insert(vec![expr]); + }, + } + } + + match_expr_list +} + #[macro_export] macro_rules! unwrap_cargo_metadata { ($cx: ident, $lint: ident, $deps: expr) => {{ diff --git a/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs b/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs index 52d3c2c1daf..d02603d7702 100644 --- a/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs +++ b/src/tools/clippy/clippy_lints/src/utils/numeric_literal.rs @@ -1,6 +1,6 @@ use rustc_ast::ast::{Lit, LitFloatType, LitIntType, LitKind}; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Copy, Clone)] pub enum Radix { Binary, Octal, @@ -11,8 +11,8 @@ pub enum Radix { impl Radix { /// Returns a reasonable digit group size for this radix. #[must_use] - fn suggest_grouping(&self) -> usize { - match *self { + fn suggest_grouping(self) -> usize { + match self { Self::Binary | Self::Hexadecimal => 4, Self::Octal | Self::Decimal => 3, } diff --git a/src/tools/clippy/clippy_lints/src/utils/paths.rs b/src/tools/clippy/clippy_lints/src/utils/paths.rs index 7566da80982..cd72fdd61fd 100644 --- a/src/tools/clippy/clippy_lints/src/utils/paths.rs +++ b/src/tools/clippy/clippy_lints/src/utils/paths.rs @@ -31,10 +31,10 @@ pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"]; pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"]; pub const DROP: [&str; 3] = ["core", "mem", "drop"]; pub const DURATION: [&str; 3] = ["core", "time", "Duration"]; -pub const EARLY_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "EarlyContext"]; +pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"]; pub const EXIT: [&str; 3] = ["std", "process", "exit"]; -pub const F32_EPSILON: [&str; 2] = ["f32", "EPSILON"]; -pub const F64_EPSILON: [&str; 2] = ["f64", "EPSILON"]; +pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"]; +pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"]; pub const FILE: [&str; 3] = ["std", "fs", "File"]; pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"]; pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"]; @@ -46,7 +46,7 @@ pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"]; pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"]; pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"]; pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"]; -pub const HASH: [&str; 2] = ["hash", "Hash"]; +pub const HASH: [&str; 3] = ["core", "hash", "Hash"]; pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"]; pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"]; pub const HASHSET: [&str; 5] = ["std", "collections", "hash", "set", "HashSet"]; @@ -57,7 +57,7 @@ pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "Into pub const IO_READ: [&str; 3] = ["std", "io", "Read"]; pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"]; pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"]; -pub const LATE_CONTEXT: [&str; 4] = ["rustc", "lint", "context", "LateContext"]; +pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"]; pub const LINT: [&str; 3] = ["rustc_session", "lint", "Lint"]; pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"]; @@ -85,13 +85,15 @@ pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"]; pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"]; pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"]; pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"]; -pub const PTR_NULL: [&str; 2] = ["ptr", "null"]; -pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"]; +pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"]; +pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"]; pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"]; pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"]; pub const RC: [&str; 3] = ["alloc", "rc", "Rc"]; pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"]; pub const RECEIVER: [&str; 4] = ["std", "sync", "mpsc", "Receiver"]; +pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"]; +pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"]; pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"]; pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"]; pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"]; @@ -104,7 +106,7 @@ pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"]; pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"]; pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGuard"]; pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"]; -pub const SERDE_DESERIALIZE: [&str; 2] = ["_serde", "Deserialize"]; +pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"]; pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"]; pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"]; pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"]; diff --git a/src/tools/clippy/clippy_lints/src/utils/usage.rs b/src/tools/clippy/clippy_lints/src/utils/usage.rs index 2fd6046ebcf..8b327b2d467 100644 --- a/src/tools/clippy/clippy_lints/src/utils/usage.rs +++ b/src/tools/clippy/clippy_lints/src/utils/usage.rs @@ -19,12 +19,11 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> used_mutably: FxHashSet::default(), skip: false, }; - let def_id = expr.hir_id.owner.to_def_id(); cx.tcx.infer_ctxt().enter(|infcx| { ExprUseVisitor::new( &mut delegate, &infcx, - def_id.expect_local(), + expr.hir_id.owner, cx.param_env, cx.typeck_results(), ) |
