Auto merge of #94929 - flip1995:clippyup, r=Manishearth

Update Clippy

r? `@Manishearth`

A few days delayed, because I recovered from a cold last week and couldn't get myself to do the sync, sorry. 🙃
This commit is contained in:
bors 2022-03-14 16:24:12 +00:00
commit 285fa7ecd0
92 changed files with 3352 additions and 333 deletions

View File

@ -624,10 +624,10 @@ dependencies = [
"futures 0.3.19",
"if_chain",
"itertools",
"num_cpus",
"parking_lot",
"quote",
"regex",
"rustc-semver",
"rustc-workspace-hack",
"rustc_tools_util 0.2.0",
"semver",

View File

@ -74,10 +74,3 @@ jobs:
run: bash .github/driver.sh
env:
OS: ${{ runner.os }}
- name: Test cargo dev new lint
run: |
cargo dev new_lint --name new_early_pass --pass early
cargo dev new_lint --name new_late_pass --pass late
cargo check
git reset --hard HEAD

View File

@ -143,13 +143,6 @@ jobs:
env:
OS: ${{ runner.os }}
- name: Test cargo dev new lint
run: |
cargo dev new_lint --name new_early_pass --pass early
cargo dev new_lint --name new_late_pass --pass late
cargo check
git reset --hard HEAD
integration_build:
needs: changelog
runs-on: ubuntu-latest

View File

@ -36,6 +36,13 @@ jobs:
- name: Test fmt
run: cargo dev fmt --check
- name: Test cargo dev new lint
run: |
cargo dev new_lint --name new_early_pass --pass early
cargo dev new_lint --name new_late_pass --pass late
cargo check
git reset --hard HEAD
# These jobs doesn't actually test anything, but they're only used to tell
# bors the build completed, as there is no practical way to detect when a
# workflow is successful listening to webhooks only.

View File

@ -3042,6 +3042,7 @@ Released 2018-09-13
<!-- lint disable no-unused-definitions -->
<!-- begin autogenerated links to lint list -->
[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
@ -3076,6 +3077,7 @@ Released 2018-09-13
[`cast_ptr_alignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ptr_alignment
[`cast_ref_to_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_ref_to_mut
[`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss
[`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes
[`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8
[`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp
[`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp
@ -3225,6 +3227,7 @@ Released 2018-09-13
[`iter_nth_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_nth_zero
[`iter_overeager_cloned`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_overeager_cloned
[`iter_skip_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_skip_next
[`iter_with_drain`]: https://rust-lang.github.io/rust-clippy/master/index.html#iter_with_drain
[`iterator_step_by_zero`]: https://rust-lang.github.io/rust-clippy/master/index.html#iterator_step_by_zero
[`just_underscores_and_digits`]: https://rust-lang.github.io/rust-clippy/master/index.html#just_underscores_and_digits
[`large_const_arrays`]: https://rust-lang.github.io/rust-clippy/master/index.html#large_const_arrays
@ -3297,6 +3300,7 @@ Released 2018-09-13
[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
[`mistyped_literal_suffixes`]: https://rust-lang.github.io/rust-clippy/master/index.html#mistyped_literal_suffixes
[`mixed_case_hex_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#mixed_case_hex_literals
[`mod_module_files`]: https://rust-lang.github.io/rust-clippy/master/index.html#mod_module_files
@ -3327,6 +3331,7 @@ Released 2018-09-13
[`needless_for_each`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_for_each
[`needless_late_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_late_init
[`needless_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_lifetimes
[`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match
[`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref
[`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value
[`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark
@ -3351,6 +3356,7 @@ Released 2018-09-13
[`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref
[`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes
[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
[`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
[`option_env_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_env_unwrap
@ -3498,6 +3504,7 @@ Released 2018-09-13
[`unit_return_expecting_ord`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_return_expecting_ord
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed

View File

@ -50,7 +50,7 @@ syn = { version = "1.0", features = ["full"] }
futures = "0.3"
parking_lot = "0.11.2"
tokio = { version = "1", features = ["io-util"] }
num_cpus = "1.13"
rustc-semver = "1.1"
[build-dependencies]
rustc_tools_util = { version = "0.2", path = "rustc_tools_util" }

View File

@ -255,7 +255,38 @@ declare_clippy_lint! {
"usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for attributes that allow lints without a reason.
///
/// (This requires the `lint_reasons` feature)
///
/// ### Why is this bad?
/// Allowing a lint should always have a reason. This reason should be documented to
/// ensure that others understand the reasoning
///
/// ### Example
/// Bad:
/// ```rust
/// #![feature(lint_reasons)]
///
/// #![allow(clippy::some_lint)]
/// ```
///
/// Good:
/// ```rust
/// #![feature(lint_reasons)]
///
/// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
/// ```
#[clippy::version = "1.61.0"]
pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
restriction,
"ensures that all `allow` and `expect` attributes have a reason"
}
declare_lint_pass!(Attributes => [
ALLOW_ATTRIBUTES_WITHOUT_REASON,
INLINE_ALWAYS,
DEPRECATED_SEMVER,
USELESS_ATTRIBUTE,
@ -269,6 +300,9 @@ impl<'tcx> LateLintPass<'tcx> for Attributes {
if is_lint_level(ident.name) {
check_clippy_lint_names(cx, ident.name, items);
}
if matches!(ident.name, sym::allow | sym::expect) {
check_lint_reason(cx, ident.name, items, attr);
}
if items.is_empty() || !attr.has_name(sym::deprecated) {
return;
}
@ -404,6 +438,30 @@ fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMe
}
}
fn check_lint_reason(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem], attr: &'_ Attribute) {
// Check for the feature
if !cx.tcx.sess.features_untracked().lint_reasons {
return;
}
// Check if the reason is present
if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
&& let MetaItemKind::NameValue(_) = &item.kind
&& item.path == sym::reason
{
return;
}
span_lint_and_help(
cx,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
attr.span,
&format!("`{}` attribute without specifying a reason", name.as_str()),
None,
"try adding a reason at the end with `, reason = \"..\"`",
);
}
fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
if let ItemKind::Fn(_, _, eid) = item.kind {
is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value)
@ -659,5 +717,5 @@ fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
}
fn is_lint_level(symbol: Symbol) -> bool {
matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid)
matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
}

View File

@ -0,0 +1,117 @@
use clippy_utils::{diagnostics::span_lint_and_then, meets_msrv, msrvs, source::snippet_opt};
use if_chain::if_chain;
use rustc_ast::Mutability;
use rustc_hir::{Expr, ExprKind, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, layout::LayoutOf, Ty, TypeAndMut};
use rustc_semver::RustcVersion;
use super::CAST_SLICE_DIFFERENT_SIZES;
fn is_child_of_cast(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let map = cx.tcx.hir();
if_chain! {
if let Some(parent_id) = map.find_parent_node(expr.hir_id);
if let Some(parent) = map.find(parent_id);
then {
let expr = match parent {
Node::Block(block) => {
if let Some(parent_expr) = block.expr {
parent_expr
} else {
return false;
}
},
Node::Expr(expr) => expr,
_ => return false,
};
matches!(expr.kind, ExprKind::Cast(..))
} else {
false
}
}
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Option<RustcVersion>) {
// suggestion is invalid if `ptr::slice_from_raw_parts` does not exist
if !meets_msrv(msrv.as_ref(), &msrvs::PTR_SLICE_RAW_PARTS) {
return;
}
// if this cast is the child of another cast expression then don't emit something for it, the full
// chain will be analyzed
if is_child_of_cast(cx, expr) {
return;
}
if let Some((from_slice_ty, to_slice_ty)) = expr_cast_chain_tys(cx, expr) {
if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(from_slice_ty.ty), cx.layout_of(to_slice_ty.ty)) {
let from_size = from_layout.size.bytes();
let to_size = to_layout.size.bytes();
if from_size != to_size && from_size != 0 && to_size != 0 {
span_lint_and_then(
cx,
CAST_SLICE_DIFFERENT_SIZES,
expr.span,
&format!(
"casting between raw pointers to `[{}]` (element size {}) and `[{}]` (element size {}) does not adjust the count",
from_slice_ty, from_size, to_slice_ty, to_size,
),
|diag| {
let cast_expr = match expr.kind {
ExprKind::Cast(cast_expr, ..) => cast_expr,
_ => unreachable!("expr should be a cast as checked by expr_cast_chain_tys"),
};
let ptr_snippet = snippet_opt(cx, cast_expr.span).unwrap();
let (mutbl_fn_str, mutbl_ptr_str) = match to_slice_ty.mutbl {
Mutability::Mut => ("_mut", "mut"),
Mutability::Not => ("", "const"),
};
let sugg = format!(
"core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {to_slice_ty}, ..)"
);
diag.span_suggestion(
expr.span,
&format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"),
sugg,
rustc_errors::Applicability::HasPlaceholders,
);
},
);
}
}
}
}
/// Returns the type T of the pointed to *const [T] or *mut [T] and the mutability of the slice if
/// the type is one of those slices
fn get_raw_slice_ty_mut(ty: Ty<'_>) -> Option<TypeAndMut<'_>> {
match ty.kind() {
ty::RawPtr(TypeAndMut { ty: slice_ty, mutbl }) => match slice_ty.kind() {
ty::Slice(ty) => Some(TypeAndMut { ty: *ty, mutbl: *mutbl }),
_ => None,
},
_ => None,
}
}
/// Returns the pair (original ptr T, final ptr U) if the expression is composed of casts
/// Returns None if the expr is not a Cast
fn expr_cast_chain_tys<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<(TypeAndMut<'tcx>, TypeAndMut<'tcx>)> {
if let ExprKind::Cast(cast_expr, _cast_to_hir_ty) = expr.peel_blocks().kind {
let cast_to = cx.typeck_results().expr_ty(expr);
let to_slice_ty = get_raw_slice_ty_mut(cast_to)?;
if let Some((inner_from_ty, _inner_to_ty)) = expr_cast_chain_tys(cx, cast_expr) {
Some((inner_from_ty, to_slice_ty))
} else {
let cast_from = cx.typeck_results().expr_ty(cast_expr);
let from_slice_ty = get_raw_slice_ty_mut(cast_from)?;
Some((from_slice_ty, to_slice_ty))
}
} else {
None
}
}

View File

@ -5,6 +5,7 @@ mod cast_precision_loss;
mod cast_ptr_alignment;
mod cast_ref_to_mut;
mod cast_sign_loss;
mod cast_slice_different_sizes;
mod char_lit_as_u8;
mod fn_to_numeric_cast;
mod fn_to_numeric_cast_any;
@ -409,6 +410,50 @@ declare_clippy_lint! {
"casts from an enum type to an integral type which will truncate the value"
}
declare_clippy_lint! {
/// Checks for `as` casts between raw pointers to slices with differently sized elements.
///
/// ### Why is this bad?
/// The produced raw pointer to a slice does not update its length metadata. The produced
/// pointer will point to a different number of bytes than the original pointer because the
/// length metadata of a raw slice pointer is in elements rather than bytes.
/// Producing a slice reference from the raw pointer will either create a slice with
/// less data (which can be surprising) or create a slice with more data and cause Undefined Behavior.
///
/// ### Example
/// // Missing data
/// ```rust
/// let a = [1_i32, 2, 3, 4];
/// let p = &a as *const [i32] as *const [u8];
/// unsafe {
/// println!("{:?}", &*p);
/// }
/// ```
/// // Undefined Behavior (note: also potential alignment issues)
/// ```rust
/// let a = [1_u8, 2, 3, 4];
/// let p = &a as *const [u8] as *const [u32];
/// unsafe {
/// println!("{:?}", &*p);
/// }
/// ```
/// Instead use `ptr::slice_from_raw_parts` to construct a slice from a data pointer and the correct length
/// ```rust
/// let a = [1_i32, 2, 3, 4];
/// let old_ptr = &a as *const [i32];
/// // The data pointer is cast to a pointer to the target `u8` not `[u8]`
/// // The length comes from the known length of 4 i32s times the 4 bytes per i32
/// let new_ptr = core::ptr::slice_from_raw_parts(old_ptr as *const u8, 16);
/// unsafe {
/// println!("{:?}", &*new_ptr);
/// }
/// ```
#[clippy::version = "1.60.0"]
pub CAST_SLICE_DIFFERENT_SIZES,
correctness,
"casting using `as` between raw pointers to slices of types with different sizes"
}
pub struct Casts {
msrv: Option<RustcVersion>,
}
@ -428,6 +473,7 @@ impl_lint_pass!(Casts => [
CAST_LOSSLESS,
CAST_REF_TO_MUT,
CAST_PTR_ALIGNMENT,
CAST_SLICE_DIFFERENT_SIZES,
UNNECESSARY_CAST,
FN_TO_NUMERIC_CAST_ANY,
FN_TO_NUMERIC_CAST,
@ -478,6 +524,8 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
cast_ref_to_mut::check(cx, expr);
cast_ptr_alignment::check(cx, expr);
char_lit_as_u8::check(cx, expr);
ptr_as_ptr::check(cx, expr, &self.msrv);
cast_slice_different_sizes::check(cx, expr, &self.msrv);
}
extract_msrv_attr!(LateContext);

View File

@ -1,4 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_in_test_function;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
@ -35,6 +36,10 @@ impl LateLintPass<'_> for DbgMacro {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
// we make an exception for test code
if is_in_test_function(cx.tcx, expr.hir_id) {
return;
}
let mut applicability = Applicability::MachineApplicable;
let suggestion = match expr.peel_drop_temps().kind {
// dbg!()

View File

@ -25,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::FN_TO_NUMERIC_CAST),
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
@ -109,6 +110,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(loops::ITER_NEXT_LOOP),
LintId::of(loops::MANUAL_FLATTEN),
LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP),
LintId::of(loops::MUT_RANGE_BOUND),
LintId::of(loops::NEEDLESS_COLLECT),
LintId::of(loops::NEEDLESS_RANGE_LOOP),
@ -136,6 +138,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(matches::MATCH_OVERLAPPING_ARM),
LintId::of(matches::MATCH_REF_PATS),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
LintId::of(matches::SINGLE_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
@ -163,6 +166,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::ITER_NTH_ZERO),
LintId::of(methods::ITER_OVEREAGER_CLONED),
LintId::of(methods::ITER_SKIP_NEXT),
LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(methods::MANUAL_FILTER_MAP),
LintId::of(methods::MANUAL_FIND_MAP),
LintId::of(methods::MANUAL_SATURATING_ARITHMETIC),
@ -189,6 +193,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::SUSPICIOUS_SPLITN),
LintId::of(methods::UNINIT_ASSUMED_INIT),
LintId::of(methods::UNNECESSARY_FILTER_MAP),
LintId::of(methods::UNNECESSARY_FIND_MAP),
LintId::of(methods::UNNECESSARY_FOLD),
LintId::of(methods::UNNECESSARY_LAZY_EVALUATIONS),
LintId::of(methods::UNNECESSARY_TO_OWNED),
@ -231,6 +236,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(non_expressive_names::JUST_UNDERSCORES_AND_DIGITS),
LintId::of(non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(open_options::NONSENSICAL_OPEN_OPTIONS),
LintId::of(option_env_unwrap::OPTION_ENV_UNWRAP),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),

View File

@ -30,6 +30,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(methods::BIND_INSTEAD_OF_MAP),
LintId::of(methods::CLONE_ON_COPY),
@ -49,6 +50,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(methods::SEARCH_IS_SOME),
LintId::of(methods::SKIP_WHILE_NEXT),
LintId::of(methods::UNNECESSARY_FILTER_MAP),
LintId::of(methods::UNNECESSARY_FIND_MAP),
LintId::of(methods::USELESS_ASREF),
LintId::of(misc::SHORT_CIRCUIT_STATEMENT),
LintId::of(misc_early::UNNEEDED_WILDCARD_PATTERN),
@ -63,6 +65,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD),
LintId::of(no_effect::NO_EFFECT),
LintId::of(no_effect::UNNECESSARY_OPERATION),
LintId::of(only_used_in_recursion::ONLY_USED_IN_RECURSION),
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
LintId::of(precedence::PRECEDENCE),

View File

@ -13,6 +13,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(bit_mask::INEFFECTIVE_BIT_MASK),
LintId::of(booleans::LOGIC_BUG),
LintId::of(casts::CAST_REF_TO_MUT),
LintId::of(casts::CAST_SLICE_DIFFERENT_SIZES),
LintId::of(copies::IFS_SAME_COND),
LintId::of(copies::IF_SAME_THEN_ELSE),
LintId::of(derive::DERIVE_HASH_XOR_EQ),

View File

@ -14,6 +14,7 @@ store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
LintId::of(utils::internal_lints::LINT_WITHOUT_LINT_PASS),
LintId::of(utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM),
LintId::of(utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE),
LintId::of(utils::internal_lints::MISSING_MSRV_ATTR_IMPL),
LintId::of(utils::internal_lints::OUTER_EXPN_EXPN_DATA),
LintId::of(utils::internal_lints::PRODUCE_ICE),
LintId::of(utils::internal_lints::UNNECESSARY_SYMBOL_STR),

View File

@ -26,6 +26,8 @@ store.register_lints(&[
#[cfg(feature = "internal")]
utils::internal_lints::MISSING_CLIPPY_VERSION_ATTRIBUTE,
#[cfg(feature = "internal")]
utils::internal_lints::MISSING_MSRV_ATTR_IMPL,
#[cfg(feature = "internal")]
utils::internal_lints::OUTER_EXPN_EXPN_DATA,
#[cfg(feature = "internal")]
utils::internal_lints::PRODUCE_ICE,
@ -42,6 +44,7 @@ store.register_lints(&[
assign_ops::ASSIGN_OP_PATTERN,
assign_ops::MISREFACTORED_ASSIGN_OP,
async_yields_async::ASYNC_YIELDS_ASYNC,
attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON,
attrs::BLANKET_CLIPPY_RESTRICTION_LINTS,
attrs::DEPRECATED_CFG_ATTR,
attrs::DEPRECATED_SEMVER,
@ -75,6 +78,7 @@ store.register_lints(&[
casts::CAST_PTR_ALIGNMENT,
casts::CAST_REF_TO_MUT,
casts::CAST_SIGN_LOSS,
casts::CAST_SLICE_DIFFERENT_SIZES,
casts::CHAR_LIT_AS_U8,
casts::FN_TO_NUMERIC_CAST,
casts::FN_TO_NUMERIC_CAST_ANY,
@ -219,6 +223,7 @@ store.register_lints(&[
loops::ITER_NEXT_LOOP,
loops::MANUAL_FLATTEN,
loops::MANUAL_MEMCPY,
loops::MISSING_SPIN_LOOP,
loops::MUT_RANGE_BOUND,
loops::NEEDLESS_COLLECT,
loops::NEEDLESS_RANGE_LOOP,
@ -255,6 +260,7 @@ store.register_lints(&[
matches::MATCH_SINGLE_BINDING,
matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
matches::MATCH_WILD_ERR_ARM,
matches::NEEDLESS_MATCH,
matches::REDUNDANT_PATTERN_MATCHING,
matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
matches::SINGLE_MATCH,
@ -296,6 +302,7 @@ store.register_lints(&[
methods::ITER_NTH_ZERO,
methods::ITER_OVEREAGER_CLONED,
methods::ITER_SKIP_NEXT,
methods::ITER_WITH_DRAIN,
methods::MANUAL_FILTER_MAP,
methods::MANUAL_FIND_MAP,
methods::MANUAL_SATURATING_ARITHMETIC,
@ -323,6 +330,7 @@ store.register_lints(&[
methods::SUSPICIOUS_SPLITN,
methods::UNINIT_ASSUMED_INIT,
methods::UNNECESSARY_FILTER_MAP,
methods::UNNECESSARY_FIND_MAP,
methods::UNNECESSARY_FOLD,
methods::UNNECESSARY_LAZY_EVALUATIONS,
methods::UNNECESSARY_TO_OWNED,
@ -392,6 +400,7 @@ store.register_lints(&[
non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
octal_escapes::OCTAL_ESCAPES,
only_used_in_recursion::ONLY_USED_IN_RECURSION,
open_options::NONSENSICAL_OPEN_OPTIONS,
option_env_unwrap::OPTION_ENV_UNWRAP,
option_if_let_else::OPTION_IF_LET_ELSE,

View File

@ -10,11 +10,13 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![
LintId::of(large_const_arrays::LARGE_CONST_ARRAYS),
LintId::of(large_enum_variant::LARGE_ENUM_VARIANT),
LintId::of(loops::MANUAL_MEMCPY),
LintId::of(loops::MISSING_SPIN_LOOP),
LintId::of(loops::NEEDLESS_COLLECT),
LintId::of(methods::EXPECT_FUN_CALL),
LintId::of(methods::EXTEND_WITH_DRAIN),
LintId::of(methods::ITER_NTH),
LintId::of(methods::ITER_OVEREAGER_CLONED),
LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(methods::MANUAL_STR_REPEAT),
LintId::of(methods::OR_FUN_CALL),
LintId::of(methods::SINGLE_CHAR_PATTERN),

View File

@ -8,6 +8,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(as_conversions::AS_CONVERSIONS),
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
LintId::of(casts::FN_TO_NUMERIC_CAST_ANY),
LintId::of(create_dir::CREATE_DIR),
LintId::of(dbg_macro::DBG_MACRO),

View File

@ -318,6 +318,7 @@ mod non_octal_unix_permissions;
mod non_send_fields_in_send_ty;
mod nonstandard_macro_braces;
mod octal_escapes;
mod only_used_in_recursion;
mod open_options;
mod option_env_unwrap;
mod option_if_let_else;
@ -505,6 +506,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(utils::internal_lints::LintWithoutLintPass::default()));
store.register_late_pass(|| Box::new(utils::internal_lints::MatchTypeOnDiagItem));
store.register_late_pass(|| Box::new(utils::internal_lints::OuterExpnDataPass));
store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl));
}
store.register_late_pass(|| Box::new(utils::author::Author));
@ -856,6 +858,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv)));
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
store.register_late_pass(|| Box::new(dbg_macro::DbgMacro));
let cargo_ignore_publish = conf.cargo_ignore_publish;
store.register_late_pass(move || {

View File

@ -0,0 +1,56 @@
use super::MISSING_SPIN_LOOP;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_no_std_crate;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::sym;
fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
match &cond.kind {
ExprKind::Block(
Block {
stmts: [],
expr: Some(e),
..
},
_,
)
| ExprKind::Unary(_, e) => unpack_cond(e),
ExprKind::Binary(_, l, r) => {
let l = unpack_cond(l);
if let ExprKind::MethodCall(..) = l.kind {
l
} else {
unpack_cond(r)
}
},
_ => cond,
}
}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &'tcx Expr<'_>) {
if_chain! {
if let ExprKind::Block(Block { stmts: [], expr: None, ..}, _) = body.kind;
if let ExprKind::MethodCall(method, [callee, ..], _) = unpack_cond(cond).kind;
if [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name);
if let ty::Adt(def, _substs) = cx.typeck_results().expr_ty(callee).kind();
if cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did());
then {
span_lint_and_sugg(
cx,
MISSING_SPIN_LOOP,
body.span,
"busy-waiting loop should at least have a spin loop hint",
"try this",
(if is_no_std_crate(cx) {
"{ core::hint::spin_loop() }"
} else {
"{ std::hint::spin_loop() }"
}).into(),
Applicability::MachineApplicable
);
}
}
}

View File

@ -7,6 +7,7 @@ mod for_loops_over_fallibles;
mod iter_next_loop;
mod manual_flatten;
mod manual_memcpy;
mod missing_spin_loop;
mod mut_range_bound;
mod needless_collect;
mod needless_range_loop;
@ -560,6 +561,42 @@ declare_clippy_lint! {
"for loops over `Option`s or `Result`s with a single expression can be simplified"
}
declare_clippy_lint! {
/// ### What it does
/// Check for empty spin loops
///
/// ### Why is this bad?
/// The loop body should have something like `thread::park()` or at least
/// `std::hint::spin_loop()` to avoid needlessly burning cycles and conserve
/// energy. Perhaps even better use an actual lock, if possible.
///
/// ### Known problems
/// This lint doesn't currently trigger on `while let` or
/// `loop { match .. { .. } }` loops, which would be considered idiomatic in
/// combination with e.g. `AtomicBool::compare_exchange_weak`.
///
/// ### Example
///
/// ```ignore
/// use core::sync::atomic::{AtomicBool, Ordering};
/// let b = AtomicBool::new(true);
/// // give a ref to `b` to another thread,wait for it to become false
/// while b.load(Ordering::Acquire) {};
/// ```
/// Use instead:
/// ```rust,no_run
///# use core::sync::atomic::{AtomicBool, Ordering};
///# let b = AtomicBool::new(true);
/// while b.load(Ordering::Acquire) {
/// std::hint::spin_loop()
/// }
/// ```
#[clippy::version = "1.59.0"]
pub MISSING_SPIN_LOOP,
perf,
"An empty busy waiting loop"
}
declare_lint_pass!(Loops => [
MANUAL_MEMCPY,
MANUAL_FLATTEN,
@ -579,6 +616,7 @@ declare_lint_pass!(Loops => [
WHILE_IMMUTABLE_CONDITION,
SAME_ITEM_PUSH,
SINGLE_ELEMENT_LOOP,
MISSING_SPIN_LOOP,
]);
impl<'tcx> LateLintPass<'tcx> for Loops {
@ -628,6 +666,7 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
if let Some(higher::While { condition, body }) = higher::While::hir(expr) {
while_immutable_condition::check(cx, condition, body);
missing_spin_loop::check(cx, condition, body);
}
needless_collect::check(expr, cx);

View File

@ -16,6 +16,7 @@ mod match_same_arms;
mod match_single_binding;
mod match_wild_enum;
mod match_wild_err_arm;
mod needless_match;
mod overlapping_arms;
mod redundant_pattern_match;
mod rest_pat_in_fully_bound_struct;
@ -566,6 +567,49 @@ declare_clippy_lint! {
"`match` with identical arm bodies"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
/// when function signatures are the same.
///
/// ### Why is this bad?
/// This `match` block does nothing and might not be what the coder intended.
///
/// ### Example
/// ```rust,ignore
/// fn foo() -> Result<(), i32> {
/// match result {
/// Ok(val) => Ok(val),
/// Err(err) => Err(err),
/// }
/// }
///
/// fn bar() -> Option<i32> {
/// if let Some(val) = option {
/// Some(val)
/// } else {
/// None
/// }
/// }
/// ```
///
/// Could be replaced as
///
/// ```rust,ignore
/// fn foo() -> Result<(), i32> {
/// result
/// }
///
/// fn bar() -> Option<i32> {
/// option
/// }
/// ```
#[clippy::version = "1.61.0"]
pub NEEDLESS_MATCH,
complexity,
"`match` or match-like `if let` that are unnecessary"
}
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
@ -599,6 +643,7 @@ impl_lint_pass!(Matches => [
REDUNDANT_PATTERN_MATCHING,
MATCH_LIKE_MATCHES_MACRO,
MATCH_SAME_ARMS,
NEEDLESS_MATCH,
]);
impl<'tcx> LateLintPass<'tcx> for Matches {
@ -622,6 +667,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
overlapping_arms::check(cx, ex, arms);
match_wild_enum::check(cx, ex, arms);
match_as_ref::check(cx, ex, arms, expr);
needless_match::check_match(cx, ex, arms);
if self.infallible_destructuring_match_linted {
self.infallible_destructuring_match_linted = false;
@ -640,6 +686,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
match_like_matches::check(cx, expr);
}
redundant_pattern_match::check(cx, expr);
needless_match::check(cx, expr);
}
}

View File

@ -0,0 +1,197 @@
use super::NEEDLESS_MATCH;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eq_expr_value, get_parent_expr, higher, is_else_clause, is_lang_ctor, peel_blocks_with_stmt};
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, BindingAnnotation, Expr, ExprKind, Pat, PatKind, Path, PathSegment, QPath, UnOp};
use rustc_lint::LateContext;
use rustc_span::sym;
pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) {
// This is for avoiding collision with `match_single_binding`.
if arms.len() < 2 {
return;
}
for arm in arms {
if let PatKind::Wild = arm.pat.kind {
let ret_expr = strip_return(arm.body);
if !eq_expr_value(cx, ex, ret_expr) {
return;
}
} else if !pat_same_as_expr(arm.pat, arm.body) {
return;
}
}
if let Some(match_expr) = get_parent_expr(cx, ex) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
match_expr.span,
"this match expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, ex.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
/// Check for nop `if let` expression that assembled as unnecessary match
///
/// ```rust,ignore
/// if let Some(a) = option {
/// Some(a)
/// } else {
/// None
/// }
/// ```
/// OR
/// ```rust,ignore
/// if let SomeEnum::A = some_enum {
/// SomeEnum::A
/// } else if let SomeEnum::B = some_enum {
/// SomeEnum::B
/// } else {
/// some_enum
/// }
/// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if_chain! {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex);
if !is_else_clause(cx.tcx, ex);
if check_if_let(cx, if_let);
then {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
ex.span,
"this if-let expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
return false;
}
// Recurrsively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
return check_if_let(cx, nested_if_let);
}
if matches!(if_else.kind, ExprKind::Block(..)) {
let else_expr = peel_blocks_with_stmt(if_else);
let ret = strip_return(else_expr);
let let_expr_ty = cx.typeck_results().expr_ty(if_let.let_expr);
if is_type_diagnostic_item(cx, let_expr_ty, sym::Option) {
if let ExprKind::Path(ref qpath) = ret.kind {
return is_lang_ctor(cx, qpath, OptionNone) || eq_expr_value(cx, if_let.let_expr, ret);
}
} else {
return eq_expr_value(cx, if_let.let_expr, ret);
}
return true;
}
}
false
}
/// Strip `return` keyword if the expression type is `ExprKind::Ret`.
fn strip_return<'hir>(expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
if let ExprKind::Ret(Some(ret)) = expr.kind {
ret
} else {
expr
}
}
fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
let expr = strip_return(expr);
match (&pat.kind, &expr.kind) {
// Example: `Some(val) => Some(val)`
(
PatKind::TupleStruct(QPath::Resolved(_, path), [first_pat, ..], _),
ExprKind::Call(call_expr, [first_param, ..]),
) => {
if let ExprKind::Path(QPath::Resolved(_, call_path)) = call_expr.kind {
if has_identical_segments(path.segments, call_path.segments)
&& has_same_non_ref_symbol(first_pat, first_param)
{
return true;
}
}
},
// Example: `val => val`, or `ref val => *val`
(PatKind::Binding(annot, _, pat_ident, _), _) => {
let new_expr = if let (
BindingAnnotation::Ref | BindingAnnotation::RefMut,
ExprKind::Unary(UnOp::Deref, operand_expr),
) = (annot, &expr.kind)
{
operand_expr
} else {
expr
};
if let ExprKind::Path(QPath::Resolved(
_,
Path {
segments: [first_seg, ..],
..
},
)) = new_expr.kind
{
return pat_ident.name == first_seg.ident.name;
}
},
// Example: `Custom::TypeA => Custom::TypeB`, or `None => None`
(PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => {
return has_identical_segments(p_path.segments, e_path.segments);
},
// Example: `5 => 5`
(PatKind::Lit(pat_lit_expr), ExprKind::Lit(expr_spanned)) => {
if let ExprKind::Lit(pat_spanned) = &pat_lit_expr.kind {
return pat_spanned.node == expr_spanned.node;
}
},
_ => {},
}
false
}
fn has_identical_segments(left_segs: &[PathSegment<'_>], right_segs: &[PathSegment<'_>]) -> bool {
if left_segs.len() != right_segs.len() {
return false;
}
for i in 0..left_segs.len() {
if left_segs[i].ident.name != right_segs[i].ident.name {
return false;
}
}
true
}
fn has_same_non_ref_symbol(pat: &Pat<'_>, expr: &Expr<'_>) -> bool {
if_chain! {
if let PatKind::Binding(annot, _, pat_ident, _) = pat.kind;
if !matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);
if let ExprKind::Path(QPath::Resolved(_, Path {segments: [first_seg, ..], .. })) = expr.kind;
then {
return pat_ident.name == first_seg.ident.name;
}
}
false
}

View File

@ -0,0 +1,72 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_integer_const;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{
higher::{self, Range},
SpanlessEq,
};
use rustc_ast::ast::RangeLimits;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use super::ITER_WITH_DRAIN;
const DRAIN_TYPES: &[Symbol] = &[sym::Vec, sym::VecDeque];
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: &Expr<'_>) {
let ty = cx.typeck_results().expr_ty(recv).peel_refs();
if let Some(drained_type) = DRAIN_TYPES.iter().find(|&&sym| is_type_diagnostic_item(cx, ty, sym)) {
// Refuse to emit `into_iter` suggestion on draining struct fields due
// to the strong possibility of processing unmovable field.
if let ExprKind::Field(..) = recv.kind {
return;
}
if let Some(range) = higher::Range::hir(arg) {
let left_full = match range {
Range { start: Some(start), .. } if is_integer_const(cx, start, 0) => true,
Range { start: None, .. } => true,
_ => false,
};
let full = left_full
&& match range {
Range {
end: Some(end),
limits: RangeLimits::HalfOpen,
..
} => {
// `x.drain(..x.len())` call
if_chain! {
if let ExprKind::MethodCall(len_path, len_args, _) = end.kind;
if len_path.ident.name == sym::len && len_args.len() == 1;
if let ExprKind::Path(QPath::Resolved(_, drain_path)) = recv.kind;
if let ExprKind::Path(QPath::Resolved(_, len_path)) = len_args[0].kind;
if SpanlessEq::new(cx).eq_path(drain_path, len_path);
then { true }
else { false }
}
},
Range {
end: None,
limits: RangeLimits::HalfOpen,
..
} => true,
_ => false,
};
if full {
span_lint_and_sugg(
cx,
ITER_WITH_DRAIN,
span.with_hi(expr.span.hi()),
&format!("`drain(..)` used on a `{}`", drained_type),
"try this",
"into_iter()".to_string(),
Applicability::MaybeIncorrect,
);
}
}
}
}

View File

@ -32,6 +32,7 @@ mod iter_nth;
mod iter_nth_zero;
mod iter_overeager_cloned;
mod iter_skip_next;
mod iter_with_drain;
mod iterator_step_by_zero;
mod manual_saturating_arithmetic;
mod manual_str_repeat;
@ -1118,6 +1119,31 @@ declare_clippy_lint! {
"using `.skip(x).next()` on an iterator"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.drain(..)` on `Vec` and `VecDeque` for iteration.
///
/// ### Why is this bad?
/// `.into_iter()` is simpler with better performance.
///
/// ### Example
/// ```rust
/// # use std::collections::HashSet;
/// let mut foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.drain(..).collect();
/// ```
/// Use instead:
/// ```rust
/// # use std::collections::HashSet;
/// let foo = vec![0, 1, 2, 3];
/// let bar: HashSet<usize> = foo.into_iter().collect();
/// ```
#[clippy::version = "1.61.0"]
pub ITER_WITH_DRAIN,
perf,
"replace `.drain(..)` with `.into_iter()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
@ -1309,7 +1335,7 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for `filter_map` calls which could be replaced by `filter` or `map`.
/// Checks for `filter_map` calls that could be replaced by `filter` or `map`.
/// More specifically it checks if the closure provided is only performing one of the
/// filter or map operations and suggests the appropriate option.
///
@ -1337,6 +1363,36 @@ declare_clippy_lint! {
"using `filter_map` when a more succinct alternative exists"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `find_map` calls that could be replaced by `find` or `map`. More
/// specifically it checks if the closure provided is only performing one of the
/// find or map operations and suggests the appropriate option.
///
/// ### Why is this bad?
/// Complexity. The intent is also clearer if only a single
/// operation is being performed.
///
/// ### Example
/// ```rust
/// let _ = (0..3).find_map(|x| if x > 2 { Some(x) } else { None });
///
/// // As there is no transformation of the argument this could be written as:
/// let _ = (0..3).find(|&x| x > 2);
/// ```
///
/// ```rust
/// let _ = (0..4).find_map(|x| Some(x + 1));
///
/// // As there is no conditional check on the argument this could be written as:
/// let _ = (0..4).map(|x| x + 1).next();
/// ```
#[clippy::version = "1.61.0"]
pub UNNECESSARY_FIND_MAP,
complexity,
"using `find_map` when a more succinct alternative exists"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `into_iter` calls on references which should be replaced by `iter`
@ -2017,9 +2073,11 @@ impl_lint_pass!(Methods => [
GET_UNWRAP,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
ITER_WITH_DRAIN,
USELESS_ASREF,
UNNECESSARY_FOLD,
UNNECESSARY_FILTER_MAP,
UNNECESSARY_FIND_MAP,
INTO_ITER_ON_REF,
SUSPICIOUS_MAP,
UNINIT_ASSUMED_INIT,
@ -2296,6 +2354,9 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
Some(("map", [_, arg], _)) => suspicious_map::check(cx, expr, recv, arg),
_ => {},
},
("drain", [arg]) => {
iter_with_drain::check(cx, expr, recv, span, arg);
},
("expect", [_]) => match method_call(recv) {
Some(("ok", [recv], _)) => ok_expect::check(cx, expr, recv),
_ => expect_used::check(cx, expr, recv),
@ -2305,9 +2366,12 @@ fn check_methods<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, msrv: Optio
extend_with_drain::check(cx, expr, recv, arg);
},
("filter_map", [arg]) => {
unnecessary_filter_map::check(cx, expr, arg);
unnecessary_filter_map::check(cx, expr, arg, name);
filter_map_identity::check(cx, expr, arg, span);
},
("find_map", [arg]) => {
unnecessary_filter_map::check(cx, expr, arg, name);
},
("flat_map", [arg]) => {
flat_map_identity::check(cx, expr, arg, span);
flat_map_option::check(cx, expr, arg, span);

View File

@ -14,10 +14,7 @@ use super::RESULT_MAP_OR_INTO_OPTION;
// The expression inside a closure may or may not have surrounding braces
// which causes problems when generating a suggestion.
fn reduce_unit_expression<'a>(
cx: &LateContext<'_>,
expr: &'a hir::Expr<'_>,
) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
fn reduce_unit_expression<'a>(expr: &'a hir::Expr<'_>) -> Option<(&'a hir::Expr<'a>, &'a [hir::Expr<'a>])> {
match expr.kind {
hir::ExprKind::Call(func, arg_char) => Some((func, arg_char)),
hir::ExprKind::Block(block, _) => {
@ -25,7 +22,7 @@ fn reduce_unit_expression<'a>(
(&[], Some(inner_expr)) => {
// If block only contains an expression,
// reduce `|x| { x + 1 }` to `|x| x + 1`
reduce_unit_expression(cx, inner_expr)
reduce_unit_expression(inner_expr)
},
_ => None,
}
@ -77,7 +74,7 @@ pub(super) fn check<'tcx>(
if let hir::ExprKind::Closure(_, _, id, span, _) = map_arg.kind;
let arg_snippet = snippet(cx, span, "..");
let body = cx.tcx.hir().body(id);
if let Some((func, [arg_char])) = reduce_unit_expression(cx, &body.value);
if let Some((func, [arg_char])) = reduce_unit_expression(&body.value);
if let Some(id) = path_def_id(cx, func).and_then(|ctor_id| cx.tcx.parent(ctor_id));
if Some(id) == cx.tcx.lang_items().option_some_variant();
then {

View File

@ -1,4 +1,6 @@
use super::utils::clone_or_copy_needed;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_copy;
use clippy_utils::usage::mutated_variables;
use clippy_utils::{is_lang_ctor, is_trait_method, path_to_local_id};
use rustc_hir as hir;
@ -9,8 +11,9 @@ use rustc_middle::ty;
use rustc_span::sym;
use super::UNNECESSARY_FILTER_MAP;
use super::UNNECESSARY_FIND_MAP;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>) {
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, name: &str) {
if !is_trait_method(cx, expr, sym::Iterator) {
return;
}
@ -20,6 +23,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
let arg_id = body.params[0].pat.hir_id;
let mutates_arg =
mutated_variables(&body.value, cx).map_or(true, |used_mutably| used_mutably.contains(&arg_id));
let (clone_or_copy_needed, _) = clone_or_copy_needed(cx, body.params[0].pat, &body.value);
let (mut found_mapping, mut found_filtering) = check_expression(cx, arg_id, &body.value);
@ -28,13 +32,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
found_mapping |= return_visitor.found_mapping;
found_filtering |= return_visitor.found_filtering;
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering {
"map"
} else if !found_mapping && !mutates_arg {
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
if name == "filter_map" { "map" } else { "map(..).next()" }
} else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) {
match cx.typeck_results().expr_ty(&body.value).kind() {
ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => {
"filter"
ty::Adt(adt, subst)
if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) =>
{
if name == "filter_map" { "filter" } else { "find" }
},
_ => return,
}
@ -43,9 +49,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
};
span_lint(
cx,
UNNECESSARY_FILTER_MAP,
if name == "filter_map" {
UNNECESSARY_FILTER_MAP
} else {
UNNECESSARY_FIND_MAP
},
expr.span,
&format!("this `.filter_map` can be written more simply using `.{}`", sugg),
&format!("this `.{}` can be written more simply using `.{}`", name, sugg),
);
}
}

View File

@ -1,14 +1,12 @@
use super::utils::clone_or_copy_needed;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait};
use clippy_utils::{fn_def_id, get_parent_expr, path_to_local_id, usage};
use clippy_utils::{fn_def_id, get_parent_expr};
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind, HirId, LangItem, Mutability, Pat};
use rustc_hir::{def_id::DefId, Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty;
use rustc_span::{sym, Symbol};
use super::UNNECESSARY_TO_OWNED;
@ -100,89 +98,6 @@ pub fn check_for_loop_iter(
false
}
/// The core logic of `check_for_loop_iter` above, this function wraps a use of
/// `CloneOrCopyVisitor`.
fn clone_or_copy_needed<'tcx>(
cx: &LateContext<'tcx>,
pat: &Pat<'tcx>,
body: &'tcx Expr<'tcx>,
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
let mut visitor = CloneOrCopyVisitor {
cx,
binding_hir_ids: pat_bindings(pat),
clone_or_copy_needed: false,
addr_of_exprs: Vec::new(),
};
visitor.visit_expr(body);
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
}
/// Returns a vector of all `HirId`s bound by the pattern.
fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
let mut collector = usage::ParamBindingIdCollector {
binding_hir_ids: Vec::new(),
};
collector.visit_pat(pat);
collector.binding_hir_ids
}
/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
/// operations performed on `binding_hir_ids` are:
/// * to take non-mutable references to them
/// * to use them as non-mutable `&self` in method calls
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
/// when `CloneOrCopyVisitor` is done visiting.
struct CloneOrCopyVisitor<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
binding_hir_ids: Vec<HirId>,
clone_or_copy_needed: bool,
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
}
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
walk_expr(self, expr);
if self.is_binding(expr) {
if let Some(parent) = get_parent_expr(self.cx, expr) {
match parent.kind {
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
self.addr_of_exprs.push(parent);
return;
},
ExprKind::MethodCall(_, args, _) => {
if_chain! {
if args.iter().skip(1).all(|arg| !self.is_binding(arg));
if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
let method_ty = self.cx.tcx.type_of(method_def_id);
let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
then {
return;
}
}
},
_ => {},
}
}
self.clone_or_copy_needed = true;
}
}
}
impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
self.binding_hir_ids
.iter()
.any(|hir_id| path_to_local_id(expr, *hir_id))
}
}
/// Returns true if the named method is `IntoIterator::into_iter`.
pub fn is_into_iter(cx: &LateContext<'_>, callee_def_id: DefId) -> bool {
cx.tcx.lang_items().require(LangItem::IntoIterIntoIter) == Ok(callee_def_id)

View File

@ -1,10 +1,14 @@
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, path_to_local_id, usage};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
use rustc_span::symbol::sym;
@ -79,3 +83,86 @@ pub(super) fn get_hint_if_single_char_arg(
}
}
}
/// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a
/// use of `CloneOrCopyVisitor`.
pub(super) fn clone_or_copy_needed<'tcx>(
cx: &LateContext<'tcx>,
pat: &Pat<'tcx>,
body: &'tcx Expr<'tcx>,
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
let mut visitor = CloneOrCopyVisitor {
cx,
binding_hir_ids: pat_bindings(pat),
clone_or_copy_needed: false,
addr_of_exprs: Vec::new(),
};
visitor.visit_expr(body);
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
}
/// Returns a vector of all `HirId`s bound by the pattern.
fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
let mut collector = usage::ParamBindingIdCollector {
binding_hir_ids: Vec::new(),
};
collector.visit_pat(pat);
collector.binding_hir_ids
}
/// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only
/// operations performed on `binding_hir_ids` are:
/// * to take non-mutable references to them
/// * to use them as non-mutable `&self` in method calls
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
/// when `CloneOrCopyVisitor` is done visiting.
struct CloneOrCopyVisitor<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
binding_hir_ids: Vec<HirId>,
clone_or_copy_needed: bool,
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
}
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
walk_expr(self, expr);
if self.is_binding(expr) {
if let Some(parent) = get_parent_expr(self.cx, expr) {
match parent.kind {
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
self.addr_of_exprs.push(parent);
return;
},
ExprKind::MethodCall(_, args, _) => {
if_chain! {
if args.iter().skip(1).all(|arg| !self.is_binding(arg));
if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id);
let method_ty = self.cx.tcx.type_of(method_def_id);
let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder();
if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not));
then {
return;
}
}
},
_ => {},
}
}
self.clone_or_copy_needed = true;
}
}
}
impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> {
fn is_binding(&self, expr: &Expr<'tcx>) -> bool {
self.binding_hir_ids
.iter()
.any(|hir_id| path_to_local_id(expr, *hir_id))
}
}

View File

@ -199,7 +199,12 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
let sugg = |diag: &mut Diagnostic| {
if let ty::Adt(def, ..) = ty.kind() {
if let Some(span) = cx.tcx.hir().span_if_local(def.did()) {
if can_type_implement_copy(cx.tcx, cx.param_env, ty, traits::ObligationCause::dummy_with_span(span)).is_ok() {
if can_type_implement_copy(
cx.tcx,
cx.param_env,
ty,
traits::ObligationCause::dummy_with_span(span),
).is_ok() {
diag.span_help(span, "consider marking this type as `Copy`");
}
}

View File

@ -0,0 +1,668 @@
use std::collections::VecDeque;
use clippy_utils::diagnostics::span_lint_and_sugg;
use itertools::{izip, Itertools};
use rustc_ast::{walk_list, Label, Mutability};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::{
Arm, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment,
QPath, Stmt, StmtKind, TyKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_middle::ty::{Ty, TyCtxt, TypeckResults};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::kw;
use rustc_span::symbol::Ident;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for arguments that are only used in recursion with no side-effects.
///
/// ### Why is this bad?
/// It could contain a useless calculation and can make function simpler.
///
/// The arguments can be involved in calculations and assignments but as long as
/// the calculations have no side-effects (function calls or mutating dereference)
/// and the assigned variables are also only in recursion, it is useless.
///
/// ### Known problems
/// In some cases, this would not catch all useless arguments.
///
/// ```rust
/// fn foo(a: usize, b: usize) -> usize {
/// let f = |x| x + 1;
///
/// if a == 0 {
/// 1
/// } else {
/// foo(a - 1, f(b))
/// }
/// }
/// ```
///
/// For example, the argument `b` is only used in recursion, but the lint would not catch it.
///
/// List of some examples that can not be caught:
/// - binary operation of non-primitive types
/// - closure usage
/// - some `break` relative operations
/// - struct pattern binding
///
/// Also, when you recurse the function name with path segments, it is not possible to detect.
///
/// ### Example
/// ```rust
/// fn f(a: usize, b: usize) -> usize {
/// if a == 0 {
/// 1
/// } else {
/// f(a - 1, b + 1)
/// }
/// }
/// # fn main() {
/// # print!("{}", f(1, 1));
/// # }
/// ```
/// Use instead:
/// ```rust
/// fn f(a: usize) -> usize {
/// if a == 0 {
/// 1
/// } else {
/// f(a - 1)
/// }
/// }
/// # fn main() {
/// # print!("{}", f(1));
/// # }
/// ```
#[clippy::version = "1.60.0"]
pub ONLY_USED_IN_RECURSION,
complexity,
"arguments that is only used in recursion can be removed"
}
declare_lint_pass!(OnlyUsedInRecursion => [ONLY_USED_IN_RECURSION]);
impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx rustc_hir::FnDecl<'tcx>,
body: &'tcx Body<'tcx>,
_: Span,
id: HirId,
) {
if let FnKind::ItemFn(ident, ..) | FnKind::Method(ident, ..) = kind {
let def_id = id.owner.to_def_id();
let data = cx.tcx.def_path(def_id).data;
if data.len() > 1 {
match data.get(data.len() - 2) {
Some(DisambiguatedDefPathData {
data: DefPathData::Impl,
disambiguator,
}) if *disambiguator != 0 => return,
_ => {},
}
}
let has_self = !matches!(decl.implicit_self, ImplicitSelfKind::None);
let ty_res = cx.typeck_results();
let param_span = body
.params
.iter()
.flat_map(|param| {
let mut v = Vec::new();
param.pat.each_binding(|_, hir_id, span, ident| {
v.push((hir_id, span, ident));
});
v
})
.skip(if has_self { 1 } else { 0 })
.filter(|(_, _, ident)| !ident.name.as_str().starts_with('_'))
.collect_vec();
let params = body.params.iter().map(|param| param.pat).collect();
let mut visitor = SideEffectVisit {
graph: FxHashMap::default(),
has_side_effect: FxHashSet::default(),
ret_vars: Vec::new(),
contains_side_effect: false,
break_vars: FxHashMap::default(),
params,
fn_ident: ident,
fn_def_id: def_id,
is_method: matches!(kind, FnKind::Method(..)),
has_self,
ty_res,
ty_ctx: cx.tcx,
};
visitor.visit_expr(&body.value);
let vars = std::mem::take(&mut visitor.ret_vars);
// this would set the return variables to side effect
visitor.add_side_effect(vars);
let mut queue = visitor.has_side_effect.iter().copied().collect::<VecDeque<_>>();
// a simple BFS to check all the variables that have side effect
while let Some(id) = queue.pop_front() {
if let Some(next) = visitor.graph.get(&id) {
for i in next {
if !visitor.has_side_effect.contains(i) {
visitor.has_side_effect.insert(*i);
queue.push_back(*i);
}
}
}
}
for (id, span, ident) in param_span {
// if the variable is not used in recursion, it would be marked as unused
if !visitor.has_side_effect.contains(&id) {
let mut queue = VecDeque::new();
let mut visited = FxHashSet::default();
queue.push_back(id);
// a simple BFS to check the graph can reach to itself
// if it can't, it means the variable is never used in recursion
while let Some(id) = queue.pop_front() {
if let Some(next) = visitor.graph.get(&id) {
for i in next {
if !visited.contains(i) {
visited.insert(id);
queue.push_back(*i);
}
}
}
}
if visited.contains(&id) {
span_lint_and_sugg(
cx,
ONLY_USED_IN_RECURSION,
span,
"parameter is only used in recursion",
"if this is intentional, prefix with an underscore",
format!("_{}", ident.name.as_str()),
Applicability::MaybeIncorrect,
);
}
}
}
}
}
}
pub fn is_primitive(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => true,
ty::Ref(_, t, _) => is_primitive(*t),
_ => false,
}
}
pub fn is_array(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Array(..) | ty::Slice(..) => true,
ty::Ref(_, t, _) => is_array(*t),
_ => false,
}
}
/// This builds the graph of side effect.
/// The edge `a -> b` means if `a` has side effect, `b` will have side effect.
///
/// There are some exmaple in following code:
/// ```rust, ignore
/// let b = 1;
/// let a = b; // a -> b
/// let (c, d) = (a, b); // c -> b, d -> b
///
/// let e = if a == 0 { // e -> a
/// c // e -> c
/// } else {
/// d // e -> d
/// };
/// ```
pub struct SideEffectVisit<'tcx> {
graph: FxHashMap<HirId, FxHashSet<HirId>>,
has_side_effect: FxHashSet<HirId>,
// bool for if the variable was dereferenced from mutable reference
ret_vars: Vec<(HirId, bool)>,
contains_side_effect: bool,
// break label
break_vars: FxHashMap<Ident, Vec<(HirId, bool)>>,
params: Vec<&'tcx Pat<'tcx>>,
fn_ident: Ident,
fn_def_id: DefId,
is_method: bool,
has_self: bool,
ty_res: &'tcx TypeckResults<'tcx>,
ty_ctx: TyCtxt<'tcx>,
}
impl<'tcx> Visitor<'tcx> for SideEffectVisit<'tcx> {
fn visit_block(&mut self, b: &'tcx Block<'tcx>) {
b.stmts.iter().for_each(|stmt| {
self.visit_stmt(stmt);
self.ret_vars.clear();
});
walk_list!(self, visit_expr, b.expr);
}
fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
match s.kind {
StmtKind::Local(Local {
pat, init: Some(init), ..
}) => {
self.visit_pat_expr(pat, init, false);
self.ret_vars.clear();
},
StmtKind::Item(i) => {
let item = self.ty_ctx.hir().item(i);
self.visit_item(item);
self.ret_vars.clear();
},
StmtKind::Expr(e) | StmtKind::Semi(e) => {
self.visit_expr(e);
self.ret_vars.clear();
},
StmtKind::Local(_) => {},
}
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
match ex.kind {
ExprKind::Array(exprs) | ExprKind::Tup(exprs) => {
self.ret_vars = exprs
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect();
},
ExprKind::Call(callee, args) => self.visit_fn(callee, args),
ExprKind::MethodCall(path, args, _) => self.visit_method_call(path, args),
ExprKind::Binary(_, lhs, rhs) => {
self.visit_bin_op(lhs, rhs);
},
ExprKind::Unary(op, expr) => self.visit_un_op(op, expr),
ExprKind::Let(Let { pat, init, .. }) => self.visit_pat_expr(pat, init, false),
ExprKind::If(bind, then_expr, else_expr) => {
self.visit_if(bind, then_expr, else_expr);
},
ExprKind::Match(expr, arms, _) => self.visit_match(expr, arms),
// since analysing the closure is not easy, just set all variables in it to side-effect
ExprKind::Closure(_, _, body_id, _, _) => {
let body = self.ty_ctx.hir().body(body_id);
self.visit_body(body);
let vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(vars);
},
ExprKind::Loop(block, label, _, _) | ExprKind::Block(block, label) => {
self.visit_block_label(block, label);
},
ExprKind::Assign(bind, expr, _) => {
self.visit_assign(bind, expr);
},
ExprKind::AssignOp(_, bind, expr) => {
self.visit_assign(bind, expr);
self.visit_bin_op(bind, expr);
},
ExprKind::Field(expr, _) => {
self.visit_expr(expr);
if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
},
ExprKind::Index(expr, index) => {
self.visit_expr(expr);
let mut vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(index);
self.ret_vars.append(&mut vars);
if !is_array(self.ty_res.expr_ty(expr)) {
self.add_side_effect(self.ret_vars.clone());
} else if matches!(self.ty_res.expr_ty(expr).kind(), ty::Ref(_, _, Mutability::Mut)) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
},
ExprKind::Break(dest, Some(expr)) => {
self.visit_expr(expr);
if let Some(label) = dest.label {
self.break_vars
.entry(label.ident)
.or_insert(Vec::new())
.append(&mut self.ret_vars);
}
self.contains_side_effect = true;
},
ExprKind::Ret(Some(expr)) => {
self.visit_expr(expr);
let vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(vars);
self.contains_side_effect = true;
},
ExprKind::Break(_, None) | ExprKind::Continue(_) | ExprKind::Ret(None) => {
self.contains_side_effect = true;
},
ExprKind::Struct(_, exprs, expr) => {
let mut ret_vars = exprs
.iter()
.flat_map(|field| {
self.visit_expr(field.expr);
std::mem::take(&mut self.ret_vars)
})
.collect();
walk_list!(self, visit_expr, expr);
self.ret_vars.append(&mut ret_vars);
},
_ => walk_expr(self, ex),
}
}
fn visit_path(&mut self, path: &'tcx Path<'tcx>, _id: HirId) {
if let Res::Local(id) = path.res {
self.ret_vars.push((id, false));
}
}
}
impl<'tcx> SideEffectVisit<'tcx> {
fn visit_assign(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
// Just support array and tuple unwrapping for now.
//
// ex) `(a, b) = (c, d);`
// The graph would look like this:
// a -> c
// b -> d
//
// This would minimize the connection of the side-effect graph.
match (&lhs.kind, &rhs.kind) {
(ExprKind::Array(lhs), ExprKind::Array(rhs)) | (ExprKind::Tup(lhs), ExprKind::Tup(rhs)) => {
// if not, it is a compile error
debug_assert!(lhs.len() == rhs.len());
izip!(*lhs, *rhs).for_each(|(lhs, rhs)| self.visit_assign(lhs, rhs));
},
// in other assigns, we have to connect all each other
// because they can be connected somehow
_ => {
self.visit_expr(lhs);
let lhs_vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(rhs);
let rhs_vars = std::mem::take(&mut self.ret_vars);
self.connect_assign(&lhs_vars, &rhs_vars, false);
},
}
}
fn visit_block_label(&mut self, block: &'tcx Block<'tcx>, label: Option<Label>) {
self.visit_block(block);
let _ = label.and_then(|label| {
self.break_vars
.remove(&label.ident)
.map(|mut break_vars| self.ret_vars.append(&mut break_vars))
});
}
fn visit_bin_op(&mut self, lhs: &'tcx Expr<'tcx>, rhs: &'tcx Expr<'tcx>) {
self.visit_expr(lhs);
let mut ret_vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(rhs);
self.ret_vars.append(&mut ret_vars);
// the binary operation between non primitive values are overloaded operators
// so they can have side-effects
if !is_primitive(self.ty_res.expr_ty(lhs)) || !is_primitive(self.ty_res.expr_ty(rhs)) {
self.ret_vars.iter().for_each(|id| {
self.has_side_effect.insert(id.0);
});
self.contains_side_effect = true;
}
}
fn visit_un_op(&mut self, op: UnOp, expr: &'tcx Expr<'tcx>) {
self.visit_expr(expr);
let ty = self.ty_res.expr_ty(expr);
// dereferencing a reference has no side-effect
if !is_primitive(ty) && !matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(..))) {
self.add_side_effect(self.ret_vars.clone());
}
if matches!((op, ty.kind()), (UnOp::Deref, ty::Ref(_, _, Mutability::Mut))) {
self.ret_vars.iter_mut().for_each(|(_, b)| *b = true);
}
}
fn visit_pat_expr(&mut self, pat: &'tcx Pat<'tcx>, expr: &'tcx Expr<'tcx>, connect_self: bool) {
match (&pat.kind, &expr.kind) {
(PatKind::Tuple(pats, _), ExprKind::Tup(exprs)) => {
self.ret_vars = izip!(*pats, *exprs)
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
},
(PatKind::Slice(front_exprs, _, back_exprs), ExprKind::Array(exprs)) => {
let mut vars = izip!(*front_exprs, *exprs)
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
self.ret_vars = izip!(back_exprs.iter().rev(), exprs.iter().rev())
.flat_map(|(pat, expr)| {
self.visit_pat_expr(pat, expr, connect_self);
std::mem::take(&mut self.ret_vars)
})
.collect();
self.ret_vars.append(&mut vars);
},
_ => {
let mut lhs_vars = Vec::new();
pat.each_binding(|_, id, _, _| lhs_vars.push((id, false)));
self.visit_expr(expr);
let rhs_vars = std::mem::take(&mut self.ret_vars);
self.connect_assign(&lhs_vars, &rhs_vars, connect_self);
self.ret_vars = rhs_vars;
},
}
}
fn visit_fn(&mut self, callee: &'tcx Expr<'tcx>, args: &'tcx [Expr<'tcx>]) {
self.visit_expr(callee);
let mut ret_vars = std::mem::take(&mut self.ret_vars);
self.add_side_effect(ret_vars.clone());
let mut is_recursive = false;
if_chain! {
if !self.has_self;
if let ExprKind::Path(QPath::Resolved(_, path)) = callee.kind;
if let Res::Def(DefKind::Fn, def_id) = path.res;
if self.fn_def_id == def_id;
then {
is_recursive = true;
}
}
if_chain! {
if !self.has_self && self.is_method;
if let ExprKind::Path(QPath::TypeRelative(ty, segment)) = callee.kind;
if segment.ident == self.fn_ident;
if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
if let Res::SelfTy{ .. } = path.res;
then {
is_recursive = true;
}
}
if is_recursive {
izip!(self.params.clone(), args).for_each(|(pat, expr)| {
self.visit_pat_expr(pat, expr, true);
self.ret_vars.clear();
});
} else {
// This would set arguments used in closure that does not have side-effect.
// Closure itself can be detected whether there is a side-effect, but the
// value of variable that is holding closure can change.
// So, we just check the variables.
self.ret_vars = args
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect_vec()
.into_iter()
.map(|id| {
self.has_side_effect.insert(id.0);
id
})
.collect();
self.contains_side_effect = true;
}
self.ret_vars.append(&mut ret_vars);
}
fn visit_method_call(&mut self, path: &'tcx PathSegment<'tcx>, args: &'tcx [Expr<'tcx>]) {
if_chain! {
if self.is_method;
if path.ident == self.fn_ident;
if let ExprKind::Path(QPath::Resolved(_, path)) = args.first().unwrap().kind;
if let Res::Local(..) = path.res;
let ident = path.segments.last().unwrap().ident;
if ident.name == kw::SelfLower;
then {
izip!(self.params.clone(), args.iter())
.for_each(|(pat, expr)| {
self.visit_pat_expr(pat, expr, true);
self.ret_vars.clear();
});
} else {
self.ret_vars = args
.iter()
.flat_map(|expr| {
self.visit_expr(expr);
std::mem::take(&mut self.ret_vars)
})
.collect_vec()
.into_iter()
.map(|a| {
self.has_side_effect.insert(a.0);
a
})
.collect();
self.contains_side_effect = true;
}
}
}
fn visit_if(&mut self, bind: &'tcx Expr<'tcx>, then_expr: &'tcx Expr<'tcx>, else_expr: Option<&'tcx Expr<'tcx>>) {
let contains_side_effect = self.contains_side_effect;
self.contains_side_effect = false;
self.visit_expr(bind);
let mut vars = std::mem::take(&mut self.ret_vars);
self.visit_expr(then_expr);
let mut then_vars = std::mem::take(&mut self.ret_vars);
walk_list!(self, visit_expr, else_expr);
if self.contains_side_effect {
self.add_side_effect(vars.clone());
}
self.contains_side_effect |= contains_side_effect;
self.ret_vars.append(&mut vars);
self.ret_vars.append(&mut then_vars);
}
fn visit_match(&mut self, expr: &'tcx Expr<'tcx>, arms: &'tcx [Arm<'tcx>]) {
self.visit_expr(expr);
let mut expr_vars = std::mem::take(&mut self.ret_vars);
self.ret_vars = arms
.iter()
.flat_map(|arm| {
let contains_side_effect = self.contains_side_effect;
self.contains_side_effect = false;
// this would visit `expr` multiple times
// but couldn't think of a better way
self.visit_pat_expr(arm.pat, expr, false);
let mut vars = std::mem::take(&mut self.ret_vars);
let _ = arm.guard.as_ref().map(|guard| {
self.visit_expr(match guard {
Guard::If(expr) | Guard::IfLet(_, expr) => expr,
});
vars.append(&mut self.ret_vars);
});
self.visit_expr(arm.body);
if self.contains_side_effect {
self.add_side_effect(vars.clone());
self.add_side_effect(expr_vars.clone());
}
self.contains_side_effect |= contains_side_effect;
vars.append(&mut self.ret_vars);
vars
})
.collect();
self.ret_vars.append(&mut expr_vars);
}
fn connect_assign(&mut self, lhs: &[(HirId, bool)], rhs: &[(HirId, bool)], connect_self: bool) {
// if mutable dereference is on assignment it can have side-effect
// (this can lead to parameter mutable dereference and change the original value)
// too hard to detect whether this value is from parameter, so this would all
// check mutable dereference assignment to side effect
lhs.iter().filter(|(_, b)| *b).for_each(|(id, _)| {
self.has_side_effect.insert(*id);
self.contains_side_effect = true;
});
// there is no connection
if lhs.is_empty() || rhs.is_empty() {
return;
}
// by connected rhs in cycle, the connections would decrease
// from `n * m` to `n + m`
// where `n` and `m` are length of `lhs` and `rhs`.
// unwrap is possible since rhs is not empty
let rhs_first = rhs.first().unwrap();
for (id, _) in lhs.iter() {
if connect_self || *id != rhs_first.0 {
self.graph
.entry(*id)
.or_insert_with(FxHashSet::default)
.insert(rhs_first.0);
}
}
let rhs = rhs.iter();
izip!(rhs.clone().cycle().skip(1), rhs).for_each(|(from, to)| {
if connect_self || from.0 != to.0 {
self.graph.entry(from.0).or_insert_with(FxHashSet::default).insert(to.0);
}
});
}
fn add_side_effect(&mut self, v: Vec<(HirId, bool)>) {
for (id, _) in v {
self.has_side_effect.insert(id);
self.contains_side_effect = true;
}
}
}

View File

@ -123,7 +123,7 @@ impl QuestionMark {
}
fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(cx, nested_expr, expr)
Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr)
}
fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool {
@ -156,9 +156,9 @@ impl QuestionMark {
}
}
fn expression_returns_unmodified_err(cx: &LateContext<'_>, expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool {
match peel_blocks_with_stmt(expr).kind {
ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(cx, ret_expr, cond_expr),
ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr),
ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr),
_ => false,
}

View File

@ -3,7 +3,7 @@ use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
use if_chain::if_chain;
use rustc_data_structures::{fx::FxHashMap, transitive_relation::TransitiveRelation};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{def_id, Body, FnDecl, HirId};
@ -512,7 +512,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
/// possible borrowers of `a`.
struct PossibleBorrowerVisitor<'a, 'tcx> {
possible_borrower: TransitiveRelation<mir::Local>,
possible_borrower: TransitiveRelation,
body: &'a mir::Body<'tcx>,
cx: &'a LateContext<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
@ -543,18 +543,10 @@ impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
continue;
}
let borrowers = self.possible_borrower.reachable_from(row);
let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
borrowers.remove(mir::Local::from_usize(0));
if !borrowers.is_empty() {
let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
for c in borrowers {
if c != mir::Local::from_usize(0) {
bs.insert(c);
}
}
if !bs.is_empty() {
map.insert(row, bs);
}
map.insert(row, borrowers);
}
}
@ -644,7 +636,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'tcx> {
/// For exampel, `_1 = &mut _2` generate _1: {_2,...}
/// Known Problems: not sure all borrowed are tracked
struct PossibleOriginVisitor<'a, 'tcx> {
possible_origin: TransitiveRelation<mir::Local>,
possible_origin: TransitiveRelation,
body: &'a mir::Body<'tcx>,
}
@ -663,18 +655,10 @@ impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
continue;
}
let borrowers = self.possible_origin.reachable_from(row);
let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
borrowers.remove(mir::Local::from_usize(0));
if !borrowers.is_empty() {
let mut bs = HybridBitSet::new_empty(self.body.local_decls.len());
for c in borrowers {
if c != mir::Local::from_usize(0) {
bs.insert(c);
}
}
if !bs.is_empty() {
map.insert(row, bs);
}
map.insert(row, borrowers);
}
}
map
@ -766,3 +750,28 @@ impl PossibleBorrowerMap<'_, '_> {
self.maybe_live.contains(local)
}
}
#[derive(Default)]
struct TransitiveRelation {
relations: FxHashMap<mir::Local, Vec<mir::Local>>,
}
impl TransitiveRelation {
fn add(&mut self, a: mir::Local, b: mir::Local) {
self.relations.entry(a).or_default().push(b);
}
fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
let mut seen = HybridBitSet::new_empty(domain_size);
let mut stack = vec![a];
while let Some(u) = stack.pop() {
if let Some(edges) = self.relations.get(&u) {
for &v in edges {
if seen.insert(v) {
stack.push(v);
}
}
}
}
seen
}
}

View File

@ -399,9 +399,9 @@ fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
match (target_opt, source_opt) {
(Some(mut target), Some(mut source)) => {
(Some(mut target), Some(source)) => {
target.reserve(source.len());
for op in source.drain(..) {
for op in source {
target.push(op);
}
Some(target)
@ -436,9 +436,9 @@ fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Exp
chained_binops_helper(left_left, left_right),
chained_binops_helper(right_left, right_right),
) {
(Some(mut left_ops), Some(mut right_ops)) => {
(Some(mut left_ops), Some(right_ops)) => {
left_ops.reserve(right_ops.len());
for op in right_ops.drain(..) {
for op in right_ops {
left_ops.push(op);
}
Some(left_ops)

View File

@ -364,6 +364,10 @@ declare_clippy_lint! {
/// ### Why is this bad?
/// The results of such a transmute are not defined.
///
/// ### Known problems
/// This lint has had multiple problems in the past and was moved to `nursery`. See issue
/// [#8496](https://github.com/rust-lang/rust-clippy/issues/8496) for more details.
///
/// ### Example
/// ```rust
/// struct Foo<T>(u32, T);

View File

@ -67,59 +67,51 @@ struct SortByKeyDetection {
/// Detect if the two expressions are mirrored (identical, except one
/// contains a and the other replaces it with b)
fn mirrored_exprs(
cx: &LateContext<'_>,
a_expr: &Expr<'_>,
a_ident: &Ident,
b_expr: &Expr<'_>,
b_ident: &Ident,
) -> bool {
fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool {
match (&a_expr.kind, &b_expr.kind) {
// Two boxes with mirrored contents
(ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => {
mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
},
// Two arrays with mirrored contents
(ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => {
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
// The two exprs are function calls.
// Check to see that the function itself and its arguments are mirrored
(ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => {
mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
&& iter::zip(*left_args, *right_args)
.all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
&& iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
// The two exprs are method calls.
// Check to see that the function is the same and the arguments are mirrored
// This is enough because the receiver of the method is listed in the arguments
(ExprKind::MethodCall(left_segment, left_args, _), ExprKind::MethodCall(right_segment, right_args, _)) => {
left_segment.ident == right_segment.ident
&& iter::zip(*left_args, *right_args)
.all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
&& iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
// Two tuples with mirrored contents
(ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => {
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(cx, left, a_ident, right, b_ident))
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
// Two binary ops, which are the same operation and which have mirrored arguments
(ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => {
left_op.node == right_op.node
&& mirrored_exprs(cx, left_left, a_ident, right_left, b_ident)
&& mirrored_exprs(cx, left_right, a_ident, right_right, b_ident)
&& mirrored_exprs(left_left, a_ident, right_left, b_ident)
&& mirrored_exprs(left_right, a_ident, right_right, b_ident)
},
// Two unary ops, which are the same operation and which have the same argument
(ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => {
left_op == right_op && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident)
left_op == right_op && mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
},
// The two exprs are literals of some kind
(ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node,
(ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(cx, left, a_ident, right, b_ident),
(ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, a_ident, right, b_ident),
(ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => {
mirrored_exprs(cx, left_block, a_ident, right_block, b_ident)
mirrored_exprs(left_block, a_ident, right_block, b_ident)
},
(ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => {
left_ident.name == right_ident.name && mirrored_exprs(cx, left_expr, a_ident, right_expr, right_ident)
left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, right_ident)
},
// Two paths: either one is a and the other is b, or they're identical to each other
(
@ -151,11 +143,9 @@ fn mirrored_exprs(
(
ExprKind::AddrOf(left_kind, Mutability::Not, left_expr),
ExprKind::AddrOf(right_kind, Mutability::Not, right_expr),
) => left_kind == right_kind && mirrored_exprs(cx, left_expr, a_ident, right_expr, b_ident),
(_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => {
mirrored_exprs(cx, a_expr, a_ident, right_expr, b_ident)
},
(ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(cx, left_expr, a_ident, b_expr, b_ident),
) => left_kind == right_kind && mirrored_exprs(left_expr, a_ident, right_expr, b_ident),
(_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => mirrored_exprs(a_expr, a_ident, right_expr, b_ident),
(ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(left_expr, a_ident, b_expr, b_ident),
_ => false,
}
}
@ -176,14 +166,13 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<LintTrigger> {
if method_path.ident.name == sym::cmp;
then {
let (closure_body, closure_arg, reverse) = if mirrored_exprs(
cx,
left_expr,
left_ident,
right_expr,
right_ident
) {
(Sugg::hir(cx, left_expr, "..").to_string(), left_ident.name.to_string(), false)
} else if mirrored_exprs(cx, left_expr, right_ident, right_expr, left_ident) {
} else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) {
(Sugg::hir(cx, left_expr, "..").to_string(), right_ident.name.to_string(), true)
} else {
return None;
@ -239,7 +228,7 @@ impl LateLintPass<'_> for UnnecessarySortBy {
if trigger.unstable { "_unstable" } else { "" },
trigger.closure_arg,
if trigger.reverse {
format!("Reverse({})", trigger.closure_body)
format!("std::cmp::Reverse({})", trigger.closure_body)
} else {
trigger.closure_body.to_string()
},

View File

@ -9,7 +9,8 @@ use rustc_hir::{
def::{CtorOf, DefKind, Res},
def_id::LocalDefId,
intravisit::{walk_inf, walk_ty, Visitor},
Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Path, QPath, TyKind,
Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion;
@ -252,6 +253,22 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
}
}
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
if_chain! {
if !pat.span.from_expansion();
if meets_msrv(self.msrv.as_ref(), &msrvs::TYPE_ALIAS_ENUM_VARIANTS);
if let Some(&StackItem::Check { impl_id, .. }) = self.stack.last();
if let PatKind::Path(QPath::Resolved(_, path)) = pat.kind;
if !matches!(path.res, Res::SelfTy { .. } | Res::Def(DefKind::TyParam, _));
if cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id);
if let [first, ..] = path.segments;
if let Some(hir_id) = first.hir_id;
then {
span_lint(cx, cx.tcx.hir().span(hir_id));
}
}
}
extract_msrv_attr!(LateContext);
}

View File

@ -25,7 +25,7 @@ use rustc_hir::{
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
use rustc_middle::hir::nested_filter;
use rustc_middle::mir::interpret::ConstValue;
use rustc_middle::ty;
use rustc_middle::ty::{self, subst::GenericArgKind};
use rustc_semver::RustcVersion;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Spanned;
@ -337,6 +337,15 @@ declare_clippy_lint! {
"found clippy lint without `clippy::version` attribute"
}
declare_clippy_lint! {
/// ### What it does
/// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV.
///
pub MISSING_MSRV_ATTR_IMPL,
internal,
"checking if all necessary steps were taken when adding a MSRV to a lint"
}
declare_lint_pass!(ClippyLintsInternal => [CLIPPY_LINTS_INTERNAL]);
impl EarlyLintPass for ClippyLintsInternal {
@ -1314,3 +1323,46 @@ fn if_chain_local_span(cx: &LateContext<'_>, local: &Local<'_>, if_chain_span: S
span.parent(),
)
}
declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]);
impl LateLintPass<'_> for MsrvAttrImpl {
fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
if_chain! {
if let hir::ItemKind::Impl(hir::Impl {
of_trait: Some(lint_pass_trait_ref),
self_ty,
items,
..
}) = &item.kind;
if let Some(lint_pass_trait_def_id) = lint_pass_trait_ref.trait_def_id();
let is_late_pass = match_def_path(cx, lint_pass_trait_def_id, &paths::LATE_LINT_PASS);
if is_late_pass || match_def_path(cx, lint_pass_trait_def_id, &paths::EARLY_LINT_PASS);
let self_ty = hir_ty_to_ty(cx.tcx, self_ty);
if let ty::Adt(self_ty_def, _) = self_ty.kind();
if self_ty_def.is_struct();
if self_ty_def.all_fields().any(|f| {
cx.tcx
.type_of(f.did)
.walk()
.filter(|t| matches!(t.unpack(), GenericArgKind::Type(_)))
.any(|t| match_type(cx, t.expect_ty(), &paths::RUSTC_VERSION))
});
if !items.iter().any(|item| item.ident.name == sym!(enter_lint_attrs));
then {
let context = if is_late_pass { "LateContext" } else { "EarlyContext" };
let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" };
let span = cx.sess().source_map().span_through_char(item.span, '{');
span_lint_and_sugg(
cx,
MISSING_MSRV_ATTR_IMPL,
span,
&format!("`extract_msrv_attr!` macro missing from `{lint_pass}` implementation"),
&format!("add `extract_msrv_attr!({context})` to the `{lint_pass}` implementation"),
format!("{}\n extract_msrv_attr!({context});", snippet(cx, span, "..")),
Applicability::MachineApplicable,
);
}
}
}
}

View File

@ -473,7 +473,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
/// ```
fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
if let Some(args) = match_lint_emission(cx, expr) {
let mut emission_info = extract_emission_info(cx, args);
let emission_info = extract_emission_info(cx, args);
if emission_info.is_empty() {
// See:
// - src/misc.rs:734:9
@ -483,7 +483,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector {
return;
}
for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
for (lint_name, applicability, is_multi_part) in emission_info {
let app_info = self.applicability_info.entry(lint_name).or_default();
app_info.applicability = applicability;
app_info.is_multi_part_suggestion = is_multi_part;
@ -693,7 +693,7 @@ fn extract_emission_info<'hir>(
}
lints
.drain(..)
.into_iter()
.map(|lint_name| (lint_name, applicability, multi_part))
.collect()
}

View File

@ -7,7 +7,7 @@ use clippy_utils::source::{snippet_opt, snippet_with_applicability};
use rustc_ast::ast::{Expr, ExprKind, Impl, Item, ItemKind, MacCall, Path, StrLit, StrStyle};
use rustc_ast::token::{self, LitKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_errors::Applicability;
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_lexer::unescape::{self, EscapeError};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_parse::parser;
@ -534,7 +534,7 @@ impl Write {
match parser
.parse_expr()
.map(rustc_ast::ptr::P::into_inner)
.map_err(|e| e.cancel())
.map_err(DiagnosticBuilder::cancel)
{
// write!(e, ...)
Ok(p) if parser.eat(&token::Comma) => Some(p),
@ -563,7 +563,7 @@ impl Write {
}
let comma_span = parser.prev_token.span;
let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|err| err.cancel()) {
let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) {
expr
} else {
return (Some(fmtstr), None);

View File

@ -279,8 +279,22 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool {
(ForeignMod(l), ForeignMod(r)) => {
both(&l.abi, &r.abi, eq_str_lit) && over(&l.items, &r.items, |l, r| eq_item(l, r, eq_foreign_item_kind))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
..
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)
@ -370,8 +384,22 @@ pub fn eq_foreign_item_kind(l: &ForeignItemKind, r: &ForeignItemKind) -> bool {
) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
..
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)
@ -402,8 +430,22 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool {
) => {
eq_defaultness(*ld, *rd) && eq_fn_sig(lf, rf) && eq_generics(lg, rg) && both(lb, rb, |l, r| eq_block(l, r))
},
(TyAlias(box ast::TyAlias { defaultness: ld, generics: lg, bounds: lb, ty: lt, .. }),
TyAlias(box ast::TyAlias { defaultness: rd, generics: rg, bounds: rb, ty: rt, .. })) => {
(
TyAlias(box ast::TyAlias {
defaultness: ld,
generics: lg,
bounds: lb,
ty: lt,
..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
..
}),
) => {
eq_defaultness(*ld, *rd)
&& eq_generics(lg, rg)
&& over(lb, rb, eq_generic_bound)

View File

@ -593,7 +593,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty().kind() {
ty::Ref(_, tam, _) => match tam.kind() {
ty::Str => String::from_utf8(
data.inner().inspect_with_uninit_and_ptr_outside_interpreter(start..end)
data.inner()
.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
.to_owned(),
)
.ok()
@ -605,7 +606,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
ty::ConstKind::Value(ConstValue::ByRef { alloc, offset: _ }) => match result.ty().kind() {
ty::Array(sub_type, len) => match sub_type.kind() {
ty::Float(FloatTy::F32) => match miri_to_const(*len) {
Some(Constant::Int(len)) => alloc.inner()
Some(Constant::Int(len)) => alloc
.inner()
.inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
.to_owned()
.chunks(4)
@ -619,7 +621,8 @@ pub fn miri_to_const(result: ty::Const<'_>) -> Option<Constant> {
_ => None,
},
ty::Float(FloatTy::F64) => match miri_to_const(*len) {
Some(Constant::Int(len)) => alloc.inner()
Some(Constant::Int(len)) => alloc
.inner()
.inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
.to_owned()
.chunks(8)

View File

@ -74,6 +74,10 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
self.inter_expr().eq_expr(left, right)
}
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
self.inter_expr().eq_path(left, right)
}
pub fn eq_path_segment(&mut self, left: &PathSegment<'_>, right: &PathSegment<'_>) -> bool {
self.inter_expr().eq_path_segment(left, right)
}
@ -362,7 +366,7 @@ impl HirEqInterExpr<'_, '_, '_> {
}
}
fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
pub fn eq_path(&mut self, left: &Path<'_>, right: &Path<'_>) -> bool {
match (left.res, right.res) {
(Res::Local(l), Res::Local(r)) => l == r || self.locals.get(&l) == Some(&r),
(Res::Local(_), _) | (_, Res::Local(_)) => false,

View File

@ -20,7 +20,7 @@ msrv_aliases! {
1,46,0 { CONST_IF_MATCH }
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2 }
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS }
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
1,38,0 { POINTER_CAST }

View File

@ -32,6 +32,8 @@ pub const DIR_BUILDER: [&str; 3] = ["std", "fs", "DirBuilder"];
pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
#[cfg(feature = "internal")]
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
#[cfg(feature = "internal")]
pub const EARLY_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "EarlyLintPass"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
pub const F64_EPSILON: [&str; 4] = ["core", "f64", "<impl f64>", "EPSILON"];
@ -67,6 +69,8 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal")]
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
#[cfg(feature = "internal")]
pub const LATE_LINT_PASS: [&str; 3] = ["rustc_lint", "passes", "LateLintPass"];
#[cfg(feature = "internal")]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
@ -126,6 +130,8 @@ pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet",
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
pub const RESULT_ERR: [&str; 4] = ["core", "result", "Result", "Err"];
pub const RESULT_OK: [&str; 4] = ["core", "result", "Result", "Ok"];
#[cfg(feature = "internal")]
pub const RUSTC_VERSION: [&str; 2] = ["rustc_semver", "RustcVersion"];
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; 3] = ["serde", "de", "Deserialize"];

View File

@ -294,7 +294,11 @@ pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symb
/// Returns `false` if the `LangItem` is not defined.
pub fn is_type_lang_item(cx: &LateContext<'_>, ty: Ty<'_>, lang_item: hir::LangItem) -> bool {
match ty.kind() {
ty::Adt(adt, _) => cx.tcx.lang_items().require(lang_item).map_or(false, |li| li == adt.did()),
ty::Adt(adt, _) => cx
.tcx
.lang_items()
.require(lang_item)
.map_or(false, |li| li == adt.did()),
_ => false,
}
}

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2022-02-24"
channel = "nightly-2022-03-14"
components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View File

@ -2,6 +2,6 @@ max_width = 120
comment_width = 100
match_block_trailing_comma = true
wrap_comments = true
edition = "2018"
edition = "2021"
error_on_line_overflow = true
version = "Two"

View File

@ -34,6 +34,7 @@ static TEST_DEPENDENCIES: &[&str] = &[
"syn",
"tokio",
"parking_lot",
"rustc_semver",
];
// Test dependencies may need an `extern crate` here to ensure that they show up
@ -53,6 +54,8 @@ extern crate parking_lot;
#[allow(unused_extern_crates)]
extern crate quote;
#[allow(unused_extern_crates)]
extern crate rustc_semver;
#[allow(unused_extern_crates)]
extern crate syn;
#[allow(unused_extern_crates)]
extern crate tokio;
@ -165,7 +168,11 @@ fn run_ui() {
let _threads = VarGuard::set(
"RUST_TEST_THREADS",
// if RUST_TEST_THREADS is set, adhere to it, otherwise override it
env::var("RUST_TEST_THREADS").unwrap_or_else(|_| num_cpus::get().to_string()),
env::var("RUST_TEST_THREADS").unwrap_or_else(|_| {
std::thread::available_parallelism()
.map_or(1, std::num::NonZeroUsize::get)
.to_string()
}),
);
compiletest::run_tests(&config);
}

View File

@ -0,0 +1,40 @@
// run-rustfix
#![deny(clippy::internal)]
#![allow(clippy::missing_clippy_version_attribute)]
#![feature(rustc_private)]
extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
#[macro_use]
extern crate rustc_session;
use clippy_utils::extract_msrv_attr;
use rustc_hir::Expr;
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_semver::RustcVersion;
declare_lint! {
pub TEST_LINT,
Warn,
""
}
struct Pass {
msrv: Option<RustcVersion>,
}
impl_lint_pass!(Pass => [TEST_LINT]);
impl LateLintPass<'_> for Pass {
extract_msrv_attr!(LateContext);
fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
}
impl EarlyLintPass for Pass {
extract_msrv_attr!(EarlyContext);
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
}
fn main() {}

View File

@ -0,0 +1,38 @@
// run-rustfix
#![deny(clippy::internal)]
#![allow(clippy::missing_clippy_version_attribute)]
#![feature(rustc_private)]
extern crate rustc_ast;
extern crate rustc_hir;
extern crate rustc_lint;
extern crate rustc_middle;
#[macro_use]
extern crate rustc_session;
use clippy_utils::extract_msrv_attr;
use rustc_hir::Expr;
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
use rustc_semver::RustcVersion;
declare_lint! {
pub TEST_LINT,
Warn,
""
}
struct Pass {
msrv: Option<RustcVersion>,
}
impl_lint_pass!(Pass => [TEST_LINT]);
impl LateLintPass<'_> for Pass {
fn check_expr(&mut self, _: &LateContext<'_>, _: &Expr<'_>) {}
}
impl EarlyLintPass for Pass {
fn check_expr(&mut self, _: &EarlyContext<'_>, _: &rustc_ast::Expr) {}
}
fn main() {}

View File

@ -0,0 +1,32 @@
error: `extract_msrv_attr!` macro missing from `LateLintPass` implementation
--> $DIR/invalid_msrv_attr_impl.rs:30:1
|
LL | impl LateLintPass<'_> for Pass {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/invalid_msrv_attr_impl.rs:3:9
|
LL | #![deny(clippy::internal)]
| ^^^^^^^^^^^^^^^^
= note: `#[deny(clippy::missing_msrv_attr_impl)]` implied by `#[deny(clippy::internal)]`
help: add `extract_msrv_attr!(LateContext)` to the `LateLintPass` implementation
|
LL + impl LateLintPass<'_> for Pass {
LL + extract_msrv_attr!(LateContext);
|
error: `extract_msrv_attr!` macro missing from `EarlyLintPass` implementation
--> $DIR/invalid_msrv_attr_impl.rs:34:1
|
LL | impl EarlyLintPass for Pass {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: add `extract_msrv_attr!(EarlyContext)` to the `EarlyLintPass` implementation
|
LL + impl EarlyLintPass for Pass {
LL + extract_msrv_attr!(EarlyContext);
|
error: aborting due to 2 previous errors

View File

@ -0,0 +1,14 @@
#![feature(lint_reasons)]
#![deny(clippy::allow_attributes_without_reason)]
// These should trigger the lint
#[allow(dead_code)]
#[allow(dead_code, deprecated)]
// These should be fine
#[allow(dead_code, reason = "This should be allowed")]
#[warn(dyn_drop, reason = "Warnings can also have reasons")]
#[warn(deref_nullptr)]
#[deny(deref_nullptr)]
#[forbid(deref_nullptr)]
fn main() {}

View File

@ -0,0 +1,23 @@
error: `allow` attribute without specifying a reason
--> $DIR/allow_attributes_without_reason.rs:5:1
|
LL | #[allow(dead_code)]
| ^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/allow_attributes_without_reason.rs:2:9
|
LL | #![deny(clippy::allow_attributes_without_reason)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= help: try adding a reason at the end with `, reason = ".."`
error: `allow` attribute without specifying a reason
--> $DIR/allow_attributes_without_reason.rs:6:1
|
LL | #[allow(dead_code, deprecated)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try adding a reason at the end with `, reason = ".."`
error: aborting due to 2 previous errors

View File

@ -0,0 +1,39 @@
fn main() {
let x: [i32; 3] = [1_i32, 2, 3];
let r_x = &x;
// Check casting through multiple bindings
// Because it's separate, it does not check the cast back to something of the same size
let a = r_x as *const [i32];
let b = a as *const [u8];
let c = b as *const [u32];
// loses data
let loss = r_x as *const [i32] as *const [u8];
// Cast back to same size but different type loses no data, just type conversion
// This is weird code but there's no reason for this lint specifically to fire *twice* on it
let restore = r_x as *const [i32] as *const [u8] as *const [u32];
// Check casting through blocks is detected
let loss_block_1 = { r_x as *const [i32] } as *const [u8];
let loss_block_2 = {
let _ = ();
r_x as *const [i32]
} as *const [u8];
// Check that resores of the same size are detected through blocks
let restore_block_1 = { r_x as *const [i32] } as *const [u8] as *const [u32];
let restore_block_2 = { ({ r_x as *const [i32] }) as *const [u8] } as *const [u32];
let restore_block_3 = {
let _ = ();
({
let _ = ();
r_x as *const [i32]
}) as *const [u8]
} as *const [u32];
// Check that the result of a long chain of casts is detected
let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
let long_chain_restore =
r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8] as *const [u32];
}

View File

@ -0,0 +1,52 @@
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:7:13
|
LL | let b = a as *const [u8];
| ^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(a as *const u8, ..)`
|
= note: `#[deny(clippy::cast_slice_different_sizes)]` on by default
error: casting between raw pointers to `[u8]` (element size 1) and `[u32]` (element size 4) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:8:13
|
LL | let c = b as *const [u32];
| ^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(b as *const u32, ..)`
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:11:16
|
LL | let loss = r_x as *const [i32] as *const [u8];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const u8, ..)`
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:18:24
|
LL | let loss_block_1 = { r_x as *const [i32] } as *const [u8];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts({ r_x as *const [i32] } as *const u8, ..)`
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:19:24
|
LL | let loss_block_2 = {
| ________________________^
LL | | let _ = ();
LL | | r_x as *const [i32]
LL | | } as *const [u8];
| |____________________^
|
help: replace with `ptr::slice_from_raw_parts`
|
LL ~ let loss_block_2 = core::ptr::slice_from_raw_parts({
LL + let _ = ();
LL + r_x as *const [i32]
LL ~ } as *const u8, ..);
|
error: casting between raw pointers to `[i32]` (element size 4) and `[u8]` (element size 1) does not adjust the count
--> $DIR/cast_slice_different_sizes.rs:36:27
|
LL | let long_chain_loss = r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const [u8];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with `ptr::slice_from_raw_parts`: `core::ptr::slice_from_raw_parts(r_x as *const [i32] as *const [u32] as *const [u16] as *const [i8] as *const u8, ..)`
error: aborting due to 6 previous errors

View File

@ -12,12 +12,12 @@ static N: AtomicUsize = AtomicUsize::new(0);
#[warn(clippy::main_recursion)]
#[start]
fn main(argc: isize, argv: *const *const u8) -> isize {
fn main(_argc: isize, _argv: *const *const u8) -> isize {
let x = N.load(Ordering::Relaxed);
N.store(x + 1, Ordering::Relaxed);
if x < 3 {
main(argc, argv);
main(_argc, _argv);
}
0

View File

@ -1,3 +1,4 @@
// compile-flags: --test
#![warn(clippy::dbg_macro)]
fn foo(n: u32) -> u32 {
@ -40,3 +41,8 @@ mod issue7274 {
dbg!(2);
});
}
#[test]
pub fn issue8481() {
dbg!(1);
}

View File

@ -1,5 +1,5 @@
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:4:22
--> $DIR/dbg_macro.rs:5:22
|
LL | if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n }
| ^^^^^^^^^^^^^^^^^^^^^^
@ -11,7 +11,7 @@ LL | if let Some(n) = n.checked_sub(4) { n } else { n }
| ~~~~~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:8:8
--> $DIR/dbg_macro.rs:9:8
|
LL | if dbg!(n <= 1) {
| ^^^^^^^^^^^^
@ -22,7 +22,7 @@ LL | if n <= 1 {
| ~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:9:9
--> $DIR/dbg_macro.rs:10:9
|
LL | dbg!(1)
| ^^^^^^^
@ -33,7 +33,7 @@ LL | 1
|
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:11:9
--> $DIR/dbg_macro.rs:12:9
|
LL | dbg!(n * factorial(n - 1))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -44,7 +44,7 @@ LL | n * factorial(n - 1)
|
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:16:5
--> $DIR/dbg_macro.rs:17:5
|
LL | dbg!(42);
| ^^^^^^^^
@ -55,7 +55,7 @@ LL | 42;
| ~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:17:5
--> $DIR/dbg_macro.rs:18:5
|
LL | dbg!(dbg!(dbg!(42)));
| ^^^^^^^^^^^^^^^^^^^^
@ -66,7 +66,7 @@ LL | dbg!(dbg!(42));
| ~~~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:18:14
--> $DIR/dbg_macro.rs:19:14
|
LL | foo(3) + dbg!(factorial(4));
| ^^^^^^^^^^^^^^^^^^
@ -77,7 +77,7 @@ LL | foo(3) + factorial(4);
| ~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:19:5
--> $DIR/dbg_macro.rs:20:5
|
LL | dbg!(1, 2, dbg!(3, 4));
| ^^^^^^^^^^^^^^^^^^^^^^
@ -88,7 +88,7 @@ LL | (1, 2, dbg!(3, 4));
| ~~~~~~~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:20:5
--> $DIR/dbg_macro.rs:21:5
|
LL | dbg!(1, 2, 3, 4, 5);
| ^^^^^^^^^^^^^^^^^^^
@ -99,7 +99,7 @@ LL | (1, 2, 3, 4, 5);
| ~~~~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
--> $DIR/dbg_macro.rs:40:9
--> $DIR/dbg_macro.rs:41:9
|
LL | dbg!(2);
| ^^^^^^^

View File

@ -1,11 +1,11 @@
// run-rustfix
#![warn(clippy::extend_with_drain)]
#![allow(clippy::iter_with_drain)]
use std::collections::BinaryHeap;
fn main() {
//gets linted
let mut vec1 = vec![0u8; 1024];
let mut vec2: std::vec::Vec<u8> = Vec::new();
vec2.append(&mut vec1);
let mut vec3 = vec![0u8; 1024];
@ -17,7 +17,7 @@ fn main() {
vec11.append(&mut return_vector());
//won't get linted it dosen't move the entire content of a vec into another
//won't get linted it doesn't move the entire content of a vec into another
let mut test1 = vec![0u8, 10];
let mut test2: std::vec::Vec<u8> = Vec::new();

View File

@ -1,11 +1,11 @@
// run-rustfix
#![warn(clippy::extend_with_drain)]
#![allow(clippy::iter_with_drain)]
use std::collections::BinaryHeap;
fn main() {
//gets linted
let mut vec1 = vec![0u8; 1024];
let mut vec2: std::vec::Vec<u8> = Vec::new();
vec2.extend(vec1.drain(..));
let mut vec3 = vec![0u8; 1024];
@ -17,7 +17,7 @@ fn main() {
vec11.extend(return_vector().drain(..));
//won't get linted it dosen't move the entire content of a vec into another
//won't get linted it doesn't move the entire content of a vec into another
let mut test1 = vec![0u8, 10];
let mut test2: std::vec::Vec<u8> = Vec::new();

View File

@ -0,0 +1,56 @@
// run-rustfix
// will emits unused mut warnings after fixing
#![allow(unused_mut)]
// will emits needless collect warnings after fixing
#![allow(clippy::needless_collect)]
#![warn(clippy::iter_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn full() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.into_iter().collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.into_iter().collect();
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn closed() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.into_iter().collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.into_iter().collect();
let mut a: HashMap<_, _> = a.into_iter().map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn should_not_help() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(1..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
let _: Vec<_> = b.drain(0..a.len()).collect();
}
#[derive(Default)]
struct Bomb {
fire: Vec<u8>,
}
fn should_not_help_0(bomb: &mut Bomb) {
let _: Vec<u8> = bomb.fire.drain(..).collect();
}
fn main() {
full();
closed();
should_not_help();
should_not_help_0(&mut Bomb::default());
}

View File

@ -0,0 +1,56 @@
// run-rustfix
// will emits unused mut warnings after fixing
#![allow(unused_mut)]
// will emits needless collect warnings after fixing
#![allow(clippy::needless_collect)]
#![warn(clippy::iter_with_drain)]
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
fn full() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..).collect();
let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn closed() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(0..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len()).collect();
let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
}
fn should_not_help() {
let mut a = vec!["aaa".to_string(), "bbb".to_string()];
let mut a: BinaryHeap<_> = a.drain(1..).collect();
let mut a: HashSet<_> = a.drain().collect();
let mut a: VecDeque<_> = a.drain().collect();
let mut a: Vec<_> = a.drain(..a.len() - 1).collect();
let mut a: HashMap<_, _> = a.drain(1..a.len() - 1).map(|x| (x.clone(), x)).collect();
let _: Vec<(String, String)> = a.drain().collect();
let mut b = vec!["aaa".to_string(), "bbb".to_string()];
let _: Vec<_> = b.drain(0..a.len()).collect();
}
#[derive(Default)]
struct Bomb {
fire: Vec<u8>,
}
fn should_not_help_0(bomb: &mut Bomb) {
let _: Vec<u8> = bomb.fire.drain(..).collect();
}
fn main() {
full();
closed();
should_not_help();
should_not_help_0(&mut Bomb::default());
}

View File

@ -0,0 +1,40 @@
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:11:34
|
LL | let mut a: BinaryHeap<_> = a.drain(..).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
|
= note: `-D clippy::iter-with-drain` implied by `-D warnings`
error: `drain(..)` used on a `VecDeque`
--> $DIR/iter_with_drain.rs:14:27
|
LL | let mut a: Vec<_> = a.drain(..).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:15:34
|
LL | let mut a: HashMap<_, _> = a.drain(..).map(|x| (x.clone(), x)).collect();
| ^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:21:34
|
LL | let mut a: BinaryHeap<_> = a.drain(0..).collect();
| ^^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `VecDeque`
--> $DIR/iter_with_drain.rs:24:27
|
LL | let mut a: Vec<_> = a.drain(..a.len()).collect();
| ^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
error: `drain(..)` used on a `Vec`
--> $DIR/iter_with_drain.rs:25:34
|
LL | let mut a: HashMap<_, _> = a.drain(0..a.len()).map(|x| (x.clone(), x)).collect();
| ^^^^^^^^^^^^^^^^^ help: try this: `into_iter()`
error: aborting due to 6 previous errors

View File

@ -148,6 +148,7 @@ fn main() {
// #7077
let s = &String::new();
#[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,

View File

@ -214,6 +214,7 @@ fn main() {
// #7077
let s = &String::new();
#[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,

View File

@ -0,0 +1,28 @@
// run-rustfix
#![warn(clippy::missing_spin_loop)]
#![allow(clippy::bool_comparison)]
#![allow(unused_braces)]
use core::sync::atomic::{AtomicBool, Ordering};
fn main() {
let b = AtomicBool::new(true);
// Those should lint
while b.load(Ordering::Acquire) { std::hint::spin_loop() }
while !b.load(Ordering::SeqCst) { std::hint::spin_loop() }
while b.load(Ordering::Acquire) == false { std::hint::spin_loop() }
while { true == b.load(Ordering::Acquire) } { std::hint::spin_loop() }
while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) { std::hint::spin_loop() }
while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) { std::hint::spin_loop() }
// This is OK, as the body is not empty
while b.load(Ordering::Acquire) {
std::hint::spin_loop()
}
// TODO: also match on loop+match or while let
}

View File

@ -0,0 +1,28 @@
// run-rustfix
#![warn(clippy::missing_spin_loop)]
#![allow(clippy::bool_comparison)]
#![allow(unused_braces)]
use core::sync::atomic::{AtomicBool, Ordering};
fn main() {
let b = AtomicBool::new(true);
// Those should lint
while b.load(Ordering::Acquire) {}
while !b.load(Ordering::SeqCst) {}
while b.load(Ordering::Acquire) == false {}
while { true == b.load(Ordering::Acquire) } {}
while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
// This is OK, as the body is not empty
while b.load(Ordering::Acquire) {
std::hint::spin_loop()
}
// TODO: also match on loop+match or while let
}

View File

@ -0,0 +1,40 @@
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:11:37
|
LL | while b.load(Ordering::Acquire) {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
|
= note: `-D clippy::missing-spin-loop` implied by `-D warnings`
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:13:37
|
LL | while !b.load(Ordering::SeqCst) {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:15:46
|
LL | while b.load(Ordering::Acquire) == false {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:17:49
|
LL | while { true == b.load(Ordering::Acquire) } {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:19:93
|
LL | while b.compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) != Ok(true) {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop.rs:21:94
|
LL | while Ok(false) != b.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) {}
| ^^ help: try this: `{ std::hint::spin_loop() }`
error: aborting due to 6 previous errors

View File

@ -0,0 +1,23 @@
// run-rustfix
#![warn(clippy::missing_spin_loop)]
#![feature(lang_items, start, libc)]
#![no_std]
use core::sync::atomic::{AtomicBool, Ordering};
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
// This should trigger the lint
let b = AtomicBool::new(true);
// This should lint with `core::hint::spin_loop()`
while b.load(Ordering::Acquire) { core::hint::spin_loop() }
0
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

View File

@ -0,0 +1,23 @@
// run-rustfix
#![warn(clippy::missing_spin_loop)]
#![feature(lang_items, start, libc)]
#![no_std]
use core::sync::atomic::{AtomicBool, Ordering};
#[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize {
// This should trigger the lint
let b = AtomicBool::new(true);
// This should lint with `core::hint::spin_loop()`
while b.load(Ordering::Acquire) {}
0
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

View File

@ -0,0 +1,10 @@
error: busy-waiting loop should at least have a spin loop hint
--> $DIR/missing_spin_loop_no_std.rs:13:37
|
LL | while b.load(Ordering::Acquire) {}
| ^^ help: try this: `{ core::hint::spin_loop() }`
|
= note: `-D clippy::missing-spin-loop` implied by `-D warnings`
error: aborting due to previous error

View File

@ -0,0 +1,83 @@
// run-rustfix
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#[derive(Clone, Copy)]
enum Choice {
A,
B,
C,
D,
}
#[allow(unused_mut)]
fn useless_match() {
let mut i = 10;
let _: i32 = i;
let _: i32 = i;
let mut _i_mut = i;
let s = "test";
let _: &str = s;
}
fn custom_type_match(se: Choice) {
let _: Choice = se;
// Don't trigger
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
_ => Choice::C,
};
// Mingled, don't trigger
let _: Choice = match se {
Choice::A => Choice::B,
Choice::B => Choice::C,
Choice::C => Choice::D,
Choice::D => Choice::A,
};
}
fn option_match(x: Option<i32>) {
let _: Option<i32> = x;
// Don't trigger, this is the case for manual_map_option
let _: Option<i32> = match x {
Some(a) => Some(-a),
None => None,
};
}
fn func_ret_err<T>(err: T) -> Result<i32, T> {
Err(err)
}
fn result_match() {
let _: Result<i32, i32> = Ok(1);
let _: Result<i32, i32> = func_ret_err(0_i32);
}
fn if_let_option() -> Option<i32> {
Some(1)
}
fn if_let_result(x: Result<(), i32>) {
let _: Result<(), i32> = x;
let _: Result<(), i32> = x;
// Input type mismatch, don't trigger
let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
}
fn if_let_custom_enum(x: Choice) {
let _: Choice = x;
// Don't trigger
let _: Choice = if let Choice::A = x {
Choice::A
} else if true {
Choice::B
} else {
x
};
}
fn main() {}

View File

@ -0,0 +1,122 @@
// run-rustfix
#![warn(clippy::needless_match)]
#![allow(clippy::manual_map)]
#![allow(dead_code)]
#[derive(Clone, Copy)]
enum Choice {
A,
B,
C,
D,
}
#[allow(unused_mut)]
fn useless_match() {
let mut i = 10;
let _: i32 = match i {
0 => 0,
1 => 1,
2 => 2,
_ => i,
};
let _: i32 = match i {
0 => 0,
1 => 1,
ref i => *i,
};
let mut _i_mut = match i {
0 => 0,
1 => 1,
ref mut i => *i,
};
let s = "test";
let _: &str = match s {
"a" => "a",
"b" => "b",
s => s,
};
}
fn custom_type_match(se: Choice) {
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
Choice::C => Choice::C,
Choice::D => Choice::D,
};
// Don't trigger
let _: Choice = match se {
Choice::A => Choice::A,
Choice::B => Choice::B,
_ => Choice::C,
};
// Mingled, don't trigger
let _: Choice = match se {
Choice::A => Choice::B,
Choice::B => Choice::C,
Choice::C => Choice::D,
Choice::D => Choice::A,
};
}
fn option_match(x: Option<i32>) {
let _: Option<i32> = match x {
Some(a) => Some(a),
None => None,
};
// Don't trigger, this is the case for manual_map_option
let _: Option<i32> = match x {
Some(a) => Some(-a),
None => None,
};
}
fn func_ret_err<T>(err: T) -> Result<i32, T> {
Err(err)
}
fn result_match() {
let _: Result<i32, i32> = match Ok(1) {
Ok(a) => Ok(a),
Err(err) => Err(err),
};
let _: Result<i32, i32> = match func_ret_err(0_i32) {
Err(err) => Err(err),
Ok(a) => Ok(a),
};
}
fn if_let_option() -> Option<i32> {
if let Some(a) = Some(1) { Some(a) } else { None }
}
fn if_let_result(x: Result<(), i32>) {
let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
// Input type mismatch, don't trigger
let _: Result<(), i32> = if let Err(e) = Ok(1) { Err(e) } else { x };
}
fn if_let_custom_enum(x: Choice) {
let _: Choice = if let Choice::A = x {
Choice::A
} else if let Choice::B = x {
Choice::B
} else if let Choice::C = x {
Choice::C
} else {
x
};
// Don't trigger
let _: Choice = if let Choice::A = x {
Choice::A
} else if true {
Choice::B
} else {
x
};
}
fn main() {}

View File

@ -0,0 +1,122 @@
error: this match expression is unnecessary
--> $DIR/needless_match.rs:17:18
|
LL | let _: i32 = match i {
| __________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | 2 => 2,
LL | | _ => i,
LL | | };
| |_____^ help: replace it with: `i`
|
= note: `-D clippy::needless-match` implied by `-D warnings`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:23:18
|
LL | let _: i32 = match i {
| __________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | ref i => *i,
LL | | };
| |_____^ help: replace it with: `i`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:28:22
|
LL | let mut _i_mut = match i {
| ______________________^
LL | | 0 => 0,
LL | | 1 => 1,
LL | | ref mut i => *i,
LL | | };
| |_____^ help: replace it with: `i`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:35:19
|
LL | let _: &str = match s {
| ___________________^
LL | | "a" => "a",
LL | | "b" => "b",
LL | | s => s,
LL | | };
| |_____^ help: replace it with: `s`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:43:21
|
LL | let _: Choice = match se {
| _____________________^
LL | | Choice::A => Choice::A,
LL | | Choice::B => Choice::B,
LL | | Choice::C => Choice::C,
LL | | Choice::D => Choice::D,
LL | | };
| |_____^ help: replace it with: `se`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:65:26
|
LL | let _: Option<i32> = match x {
| __________________________^
LL | | Some(a) => Some(a),
LL | | None => None,
LL | | };
| |_____^ help: replace it with: `x`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:81:31
|
LL | let _: Result<i32, i32> = match Ok(1) {
| _______________________________^
LL | | Ok(a) => Ok(a),
LL | | Err(err) => Err(err),
LL | | };
| |_____^ help: replace it with: `Ok(1)`
error: this match expression is unnecessary
--> $DIR/needless_match.rs:85:31
|
LL | let _: Result<i32, i32> = match func_ret_err(0_i32) {
| _______________________________^
LL | | Err(err) => Err(err),
LL | | Ok(a) => Ok(a),
LL | | };
| |_____^ help: replace it with: `func_ret_err(0_i32)`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:92:5
|
LL | if let Some(a) = Some(1) { Some(a) } else { None }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `Some(1)`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:96:30
|
LL | let _: Result<(), i32> = if let Err(e) = x { Err(e) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:97:30
|
LL | let _: Result<(), i32> = if let Ok(val) = x { Ok(val) } else { x };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x`
error: this if-let expression is unnecessary
--> $DIR/needless_match.rs:103:21
|
LL | let _: Choice = if let Choice::A = x {
| _____________________^
LL | | Choice::A
LL | | } else if let Choice::B = x {
LL | | Choice::B
... |
LL | | x
LL | | };
| |_____^ help: replace it with: `x`
error: aborting due to 12 previous errors

View File

@ -0,0 +1,122 @@
#![warn(clippy::only_used_in_recursion)]
fn simple(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { simple(a - 1, b) }
}
fn with_calc(a: usize, b: isize) -> usize {
if a == 0 { 1 } else { with_calc(a - 1, -b + 1) }
}
fn tuple((a, b): (usize, usize)) -> usize {
if a == 0 { 1 } else { tuple((a - 1, b + 1)) }
}
fn let_tuple(a: usize, b: usize) -> usize {
let (c, d) = (a, b);
if c == 0 { 1 } else { let_tuple(c - 1, d + 1) }
}
fn array([a, b]: [usize; 2]) -> usize {
if a == 0 { 1 } else { array([a - 1, b + 1]) }
}
fn index(a: usize, mut b: &[usize], c: usize) -> usize {
if a == 0 { 1 } else { index(a - 1, b, c + b[0]) }
}
fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
let c = loop {
b += 1;
c += 1;
if c == 10 {
break b;
}
};
if a == 0 { 1 } else { break_(a - 1, c, c) }
}
// this has a side effect
fn mut_ref(a: usize, b: &mut usize) -> usize {
*b = 1;
if a == 0 { 1 } else { mut_ref(a - 1, b) }
}
fn mut_ref2(a: usize, b: &mut usize) -> usize {
let mut c = *b;
if a == 0 { 1 } else { mut_ref2(a - 1, &mut c) }
}
fn not_primitive(a: usize, b: String) -> usize {
if a == 0 { 1 } else { not_primitive(a - 1, b) }
}
// this doesn't have a side effect,
// but `String` is not primitive.
fn not_primitive_op(a: usize, b: String, c: &str) -> usize {
if a == 1 { 1 } else { not_primitive_op(a, b + c, c) }
}
struct A;
impl A {
fn method(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { A::method(a - 1, b - 1) }
}
fn method2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.method2(a - 1, b + 1) }
}
}
trait B {
fn hello(a: usize, b: usize) -> usize;
fn hello2(&self, a: usize, b: usize) -> usize;
}
impl B for A {
fn hello(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { A::hello(a - 1, b + 1) }
}
fn hello2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
}
}
trait C {
fn hello(a: usize, b: usize) -> usize {
if a == 0 { 1 } else { Self::hello(a - 1, b + 1) }
}
fn hello2(&self, a: usize, b: usize) -> usize {
if a == 0 { 1 } else { self.hello2(a - 1, b + 1) }
}
}
fn ignore(a: usize, _: usize) -> usize {
if a == 1 { 1 } else { ignore(a - 1, 0) }
}
fn ignore2(a: usize, _b: usize) -> usize {
if a == 1 { 1 } else { ignore2(a - 1, _b) }
}
fn f1(a: u32) -> u32 {
a
}
fn f2(a: u32) -> u32 {
f1(a)
}
fn inner_fn(a: u32) -> u32 {
fn inner_fn(a: u32) -> u32 {
a
}
inner_fn(a)
}
fn main() {}

View File

@ -0,0 +1,82 @@
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:3:21
|
LL | fn simple(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
|
= note: `-D clippy::only-used-in-recursion` implied by `-D warnings`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:7:24
|
LL | fn with_calc(a: usize, b: isize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:11:14
|
LL | fn tuple((a, b): (usize, usize)) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:15:24
|
LL | fn let_tuple(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:20:14
|
LL | fn array([a, b]: [usize; 2]) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:24:20
|
LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
| ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:24:37
|
LL | fn index(a: usize, mut b: &[usize], c: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_c`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:28:21
|
LL | fn break_(a: usize, mut b: usize, mut c: usize) -> usize {
| ^^^^^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:46:23
|
LL | fn mut_ref2(a: usize, b: &mut usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:51:28
|
LL | fn not_primitive(a: usize, b: String) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:68:33
|
LL | fn method2(&self, a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:90:24
|
LL | fn hello(a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: parameter is only used in recursion
--> $DIR/only_used_in_recursion.rs:94:32
|
LL | fn hello2(&self, a: usize, b: usize) -> usize {
| ^ help: if this is intentional, prefix with an underscore: `_b`
error: aborting due to 13 previous errors

View File

@ -1,7 +1,7 @@
// run-rustfix
#![allow(unused_parens)]
#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}

View File

@ -1,7 +1,7 @@
// run-rustfix
#![allow(unused_parens)]
#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}

View File

@ -25,8 +25,8 @@ fn main() {
let slice_ptr = &[0, 1, 2, 3] as *const [i32];
// ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u16] };
let _ptr_to_unsized = slice_ptr as *const [u16];
let _ptr_to_unsized_transmute = unsafe { slice_ptr as *const [u32] };
let _ptr_to_unsized = slice_ptr as *const [u32];
// TODO: We could try testing vtable casts here too, but maybe
// we should wait until std::raw::TraitObject is stabilized?

View File

@ -25,8 +25,8 @@ fn main() {
let slice_ptr = &[0, 1, 2, 3] as *const [i32];
// ... or pointer_kind(T) = pointer_kind(U_0); ptr-ptr-cast
let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
let _ptr_to_unsized = slice_ptr as *const [u16];
let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
let _ptr_to_unsized = slice_ptr as *const [u32];
// TODO: We could try testing vtable casts here too, but maybe
// we should wait until std::raw::TraitObject is stabilized?

View File

@ -17,8 +17,8 @@ LL | let _ptr_i8_transmute = unsafe { transmute::<*const i32, *const i8>(ptr
error: transmute from a pointer to a pointer
--> $DIR/transmutes_expressible_as_ptr_casts.rs:28:46
|
LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u16]>(slice_ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u16]`
LL | let _ptr_to_unsized_transmute = unsafe { transmute::<*const [i32], *const [u32]>(slice_ptr) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `slice_ptr as *const [u32]`
error: transmute from `*const i32` to `usize` which could be expressed as a pointer cast instead
--> $DIR/transmutes_expressible_as_ptr_casts.rs:34:50

View File

@ -1,3 +1,5 @@
#![allow(dead_code)]
fn main() {
let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
let _ = (0..4).filter_map(|x| {
@ -19,3 +21,130 @@ fn main() {
fn filter_map_none_changes_item_type() -> impl Iterator<Item = bool> {
"".chars().filter_map(|_| None)
}
// https://github.com/rust-lang/rust-clippy/issues/4433#issue-483920107
mod comment_483920107 {
enum Severity {
Warning,
Other,
}
struct ServerError;
impl ServerError {
fn severity(&self) -> Severity {
Severity::Warning
}
}
struct S {
warnings: Vec<ServerError>,
}
impl S {
fn foo(&mut self, server_errors: Vec<ServerError>) {
#[allow(unused_variables)]
let errors: Vec<ServerError> = server_errors
.into_iter()
.filter_map(|se| match se.severity() {
Severity::Warning => {
self.warnings.push(se);
None
},
_ => Some(se),
})
.collect();
}
}
}
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-611006622
mod comment_611006622 {
struct PendingRequest {
reply_to: u8,
token: u8,
expires: u8,
group_id: u8,
}
enum Value {
Null,
}
struct Node;
impl Node {
fn send_response(&self, _reply_to: u8, _token: u8, _value: Value) -> &Self {
self
}
fn on_error_warn(&self) -> &Self {
self
}
}
struct S {
pending_requests: Vec<PendingRequest>,
}
impl S {
fn foo(&mut self, node: Node, now: u8, group_id: u8) {
// "drain_filter"
self.pending_requests = self
.pending_requests
.drain(..)
.filter_map(|pending| {
if pending.expires <= now {
return None; // Expired, remove
}
if pending.group_id == group_id {
// Matched - reuse strings and remove
node.send_response(pending.reply_to, pending.token, Value::Null)
.on_error_warn();
None
} else {
// Keep waiting
Some(pending)
}
})
.collect();
}
}
}
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-621925270
// This extrapolation doesn't reproduce the false positive. Additional context seems necessary.
mod comment_621925270 {
struct Signature(u8);
fn foo(sig_packets: impl Iterator<Item = Result<Signature, ()>>) -> impl Iterator<Item = u8> {
sig_packets.filter_map(|res| match res {
Ok(Signature(sig_packet)) => Some(sig_packet),
_ => None,
})
}
}
// https://github.com/rust-lang/rust-clippy/issues/4433#issuecomment-1052978898
mod comment_1052978898 {
#![allow(clippy::redundant_closure)]
pub struct S(u8);
impl S {
pub fn consume(self) {
println!("yum");
}
}
pub fn filter_owned() -> impl Iterator<Item = S> {
(0..10).map(|i| S(i)).filter_map(|s| {
if s.0 & 1 == 0 {
s.consume();
None
} else {
Some(s)
}
})
}
}

View File

@ -1,5 +1,5 @@
error: this `.filter_map` can be written more simply using `.filter`
--> $DIR/unnecessary_filter_map.rs:2:13
--> $DIR/unnecessary_filter_map.rs:4:13
|
LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -7,7 +7,7 @@ LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None });
= note: `-D clippy::unnecessary-filter-map` implied by `-D warnings`
error: this `.filter_map` can be written more simply using `.filter`
--> $DIR/unnecessary_filter_map.rs:3:13
--> $DIR/unnecessary_filter_map.rs:5:13
|
LL | let _ = (0..4).filter_map(|x| {
| _____________^
@ -19,7 +19,7 @@ LL | | });
| |______^
error: this `.filter_map` can be written more simply using `.filter`
--> $DIR/unnecessary_filter_map.rs:9:13
--> $DIR/unnecessary_filter_map.rs:11:13
|
LL | let _ = (0..4).filter_map(|x| match x {
| _____________^
@ -29,7 +29,7 @@ LL | | });
| |______^
error: this `.filter_map` can be written more simply using `.map`
--> $DIR/unnecessary_filter_map.rs:14:13
--> $DIR/unnecessary_filter_map.rs:16:13
|
LL | let _ = (0..4).filter_map(|x| Some(x + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,23 @@
#![allow(dead_code)]
fn main() {
let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
let _ = (0..4).find_map(|x| {
if x > 1 {
return Some(x);
};
None
});
let _ = (0..4).find_map(|x| match x {
0 | 1 => None,
_ => Some(x),
});
let _ = (0..4).find_map(|x| Some(x + 1));
let _ = (0..4).find_map(i32::checked_abs);
}
fn find_map_none_changes_item_type() -> Option<bool> {
"".chars().find_map(|_| None)
}

View File

@ -0,0 +1,38 @@
error: this `.find_map` can be written more simply using `.find`
--> $DIR/unnecessary_find_map.rs:4:13
|
LL | let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::unnecessary-find-map` implied by `-D warnings`
error: this `.find_map` can be written more simply using `.find`
--> $DIR/unnecessary_find_map.rs:5:13
|
LL | let _ = (0..4).find_map(|x| {
| _____________^
LL | | if x > 1 {
LL | | return Some(x);
LL | | };
LL | | None
LL | | });
| |______^
error: this `.find_map` can be written more simply using `.find`
--> $DIR/unnecessary_find_map.rs:11:13
|
LL | let _ = (0..4).find_map(|x| match x {
| _____________^
LL | | 0 | 1 => None,
LL | | _ => Some(x),
LL | | });
| |______^
error: this `.find_map` can be written more simply using `.map(..).next()`
--> $DIR/unnecessary_find_map.rs:16:13
|
LL | let _ = (0..4).find_map(|x| Some(x + 1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 4 previous errors

View File

@ -3,7 +3,6 @@
#![allow(clippy::stable_sort_primitive)]
use std::cell::Ref;
use std::cmp::Reverse;
fn unnecessary_sort_by() {
fn id(x: isize) -> isize {
@ -18,8 +17,8 @@ fn unnecessary_sort_by() {
vec.sort_unstable_by_key(|a| id(-a));
// Reverse examples
vec.sort_by(|a, b| b.cmp(a)); // not linted to avoid suggesting `Reverse(b)` which would borrow
vec.sort_by_key(|b| Reverse((b + 5).abs()));
vec.sort_unstable_by_key(|b| Reverse(id(-b)));
vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs()));
vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b)));
// Negative examples (shouldn't be changed)
let c = &7;
vec.sort_by(|a, b| (b - a).cmp(&(a - b)));
@ -76,7 +75,6 @@ mod issue_5754 {
// The closure parameter is not dereferenced anymore, so non-Copy types can be linted
mod issue_6001 {
use super::*;
struct Test(String);
impl Test {
@ -93,8 +91,8 @@ mod issue_6001 {
args.sort_by_key(|a| a.name());
args.sort_unstable_by_key(|a| a.name());
// Reverse
args.sort_by_key(|b| Reverse(b.name()));
args.sort_unstable_by_key(|b| Reverse(b.name()));
args.sort_by_key(|b| std::cmp::Reverse(b.name()));
args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()));
}
}

View File

@ -3,7 +3,6 @@
#![allow(clippy::stable_sort_primitive)]
use std::cell::Ref;
use std::cmp::Reverse;
fn unnecessary_sort_by() {
fn id(x: isize) -> isize {
@ -76,7 +75,6 @@ mod issue_5754 {
// The closure parameter is not dereferenced anymore, so non-Copy types can be linted
mod issue_6001 {
use super::*;
struct Test(String);
impl Test {

View File

@ -1,5 +1,5 @@
error: use Vec::sort here instead
--> $DIR/unnecessary_sort_by.rs:15:5
--> $DIR/unnecessary_sort_by.rs:14:5
|
LL | vec.sort_by(|a, b| a.cmp(b));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort()`
@ -7,70 +7,70 @@ LL | vec.sort_by(|a, b| a.cmp(b));
= note: `-D clippy::unnecessary-sort-by` implied by `-D warnings`
error: use Vec::sort here instead
--> $DIR/unnecessary_sort_by.rs:16:5
--> $DIR/unnecessary_sort_by.rs:15:5
|
LL | vec.sort_unstable_by(|a, b| a.cmp(b));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable()`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:17:5
--> $DIR/unnecessary_sort_by.rs:16:5
|
LL | vec.sort_by(|a, b| (a + 5).abs().cmp(&(b + 5).abs()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (a + 5).abs())`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:18:5
--> $DIR/unnecessary_sort_by.rs:17:5
|
LL | vec.sort_unstable_by(|a, b| id(-a).cmp(&id(-b)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| id(-a))`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:21:5
--> $DIR/unnecessary_sort_by.rs:20:5
|
LL | vec.sort_by(|a, b| (b + 5).abs().cmp(&(a + 5).abs()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| Reverse((b + 5).abs()))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|b| std::cmp::Reverse((b + 5).abs()))`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:22:5
--> $DIR/unnecessary_sort_by.rs:21:5
|
LL | vec.sort_unstable_by(|a, b| id(-b).cmp(&id(-a)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| Reverse(id(-b)))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|b| std::cmp::Reverse(id(-b)))`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:32:5
--> $DIR/unnecessary_sort_by.rs:31:5
|
LL | vec.sort_by(|a, b| (***a).abs().cmp(&(***b).abs()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_by_key(|a| (***a).abs())`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:33:5
--> $DIR/unnecessary_sort_by.rs:32:5
|
LL | vec.sort_unstable_by(|a, b| (***a).abs().cmp(&(***b).abs()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `vec.sort_unstable_by_key(|a| (***a).abs())`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:93:9
--> $DIR/unnecessary_sort_by.rs:91:9
|
LL | args.sort_by(|a, b| a.name().cmp(&b.name()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|a| a.name())`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:94:9
--> $DIR/unnecessary_sort_by.rs:92:9
|
LL | args.sort_unstable_by(|a, b| a.name().cmp(&b.name()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|a| a.name())`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:96:9
--> $DIR/unnecessary_sort_by.rs:94:9
|
LL | args.sort_by(|a, b| b.name().cmp(&a.name()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| Reverse(b.name()))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_by_key(|b| std::cmp::Reverse(b.name()))`
error: use Vec::sort_by_key here instead
--> $DIR/unnecessary_sort_by.rs:97:9
--> $DIR/unnecessary_sort_by.rs:95:9
|
LL | args.sort_unstable_by(|a, b| b.name().cmp(&a.name()));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| Reverse(b.name()))`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `args.sort_unstable_by_key(|b| std::cmp::Reverse(b.name()))`
error: aborting due to 12 previous errors

View File

@ -2,7 +2,7 @@
// aux-build:proc_macro_derive.rs
#![warn(clippy::use_self)]
#![allow(dead_code)]
#![allow(dead_code, unreachable_code)]
#![allow(
clippy::should_implement_trait,
clippy::upper_case_acronyms,
@ -519,3 +519,26 @@ mod self_is_ty_param {
}
}
}
mod use_self_in_pat {
enum Foo {
Bar,
Baz,
}
impl Foo {
fn do_stuff(self) {
match self {
Self::Bar => unimplemented!(),
Self::Baz => unimplemented!(),
}
match Some(1) {
Some(_) => unimplemented!(),
None => unimplemented!(),
}
if let Self::Bar = self {
unimplemented!()
}
}
}
}

View File

@ -2,7 +2,7 @@
// aux-build:proc_macro_derive.rs
#![warn(clippy::use_self)]
#![allow(dead_code)]
#![allow(dead_code, unreachable_code)]
#![allow(
clippy::should_implement_trait,
clippy::upper_case_acronyms,
@ -519,3 +519,26 @@ mod self_is_ty_param {
}
}
}
mod use_self_in_pat {
enum Foo {
Bar,
Baz,
}
impl Foo {
fn do_stuff(self) {
match self {
Foo::Bar => unimplemented!(),
Foo::Baz => unimplemented!(),
}
match Some(1) {
Some(_) => unimplemented!(),
None => unimplemented!(),
}
if let Foo::Bar = self {
unimplemented!()
}
}
}
}

View File

@ -168,5 +168,23 @@ error: unnecessary structure name repetition
LL | S2::new()
| ^^ help: use the applicable keyword: `Self`
error: aborting due to 28 previous errors
error: unnecessary structure name repetition
--> $DIR/use_self.rs:532:17
|
LL | Foo::Bar => unimplemented!(),
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
--> $DIR/use_self.rs:533:17
|
LL | Foo::Baz => unimplemented!(),
| ^^^ help: use the applicable keyword: `Self`
error: unnecessary structure name repetition
--> $DIR/use_self.rs:539:20
|
LL | if let Foo::Bar = self {
| ^^^ help: use the applicable keyword: `Self`
error: aborting due to 31 previous errors

View File

@ -25,7 +25,56 @@ Otherwise, have a great day =^.^=
blockquote { font-size: 1em; }
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { display: none !important; }
.form-inline .checkbox { margin-right: 0.6em }
.dropdown-menu {
color: var(--fg);
background: var(--theme-popup-bg);
border: 1px solid var(--theme-popup-border);
}
.dropdown-menu .divider {
background-color: var(--theme-popup-border);
}
.dropdown-menu .checkbox {
display: block;
white-space: nowrap;
margin: 0;
}
.dropdown-menu .checkbox label {
padding: 3px 20px;
width: 100%;
}
.dropdown-menu .checkbox input {
position: relative;
margin: 0 0.5rem 0;
padding: 0;
}
.dropdown-menu .checkbox:hover {
background-color: var(--theme-hover);
}
div.panel div.panel-body button.dropdown-toggle {
background: var(--searchbar-bg);
color: var(--searchbar-fg);
border-color: var(--theme-popup-border);
}
div.panel div.panel-body button.dropdown-toggle:hover {
box-shadow: 0 0 3px var(--searchbar-shadow-color);
}
div.panel div.panel-body .open button.dropdown-toggle {
background: var(--searchbar-bg);
color: var(--searchbar-fg);
border-color: var(--theme-popup-border);
filter: brightness(90%);
}
.dropdown-toggle .badge {
background-color: #777;
}
.panel-heading { cursor: pointer; }
@ -38,6 +87,16 @@ Otherwise, have a great day =^.^=
.panel .panel-title-name .anchor { display: none; }
.panel:hover .panel-title-name .anchor { display: inline;}
.search-control {
margin-top: 15px;
}
@media (min-width: 992px) {
.search-control {
margin-top: 0;
}
}
.label {
padding-top: 0.3em;
padding-bottom: 0.3em;
@ -143,13 +202,17 @@ Otherwise, have a great day =^.^=
--inline-code-bg: #191f26;
}
.theme-dropdown {
position: absolute;
margin: 0.7em;
z-index: 10;
}
/* Applying the mdBook theme */
.theme-icon {
position: absolute;
text-align: center;
width: 2em;
height: 2em;
margin: 0.7em;
line-height: 2em;
border: solid 1px var(--icons);
border-radius: 5px;
@ -160,23 +223,28 @@ Otherwise, have a great day =^.^=
background: var(--theme-hover);
}
.theme-choice {
position: absolute;
margin-top: calc(2em + 0.7em);
margin-left: 0.7em;
display: none;
list-style: none;
border: 1px solid var(--theme-popup-border);
border-radius: 5px;
color: var(--fg);
background: var(--theme-popup-bg);
padding: 0 0;
overflow: hidden;
}
.theme-dropdown.open .theme-choice {
display: block;
}
.theme-choice > li {
padding: 5px 10px;
font-size: 0.8em;
user-select: none;
cursor: pointer;
}
.theme-choice > li:hover {
.theme-choice>li:hover {
background: var(--theme-hover);
}
@ -240,17 +308,15 @@ Otherwise, have a great day =^.^=
</style>
</head>
<body>
<div id="theme-icon" class="theme-icon">&#128396;</div>
<ul id="theme-menu" class="theme-choice" style="display: none;">
<li id="light">Light</li>
<li id="rust">Rust</li>
<li id="coal">Coal</li>
<li id="navy">Navy</li>
<li id="ayu">Ayu</li>
</ul>
<body ng-app="clippy" ng-controller="lintList">
<div theme-dropdown class="theme-dropdown">
<div id="theme-icon" class="theme-icon">&#128396;</div>
<ul id="theme-menu" class="theme-choice">
<li id="{{id}}" ng-repeat="(id, name) in themes" ng-click="selectTheme(id)">{{name}}</li>
</ul>
</div>
<div class="container" ng-app="clippy" ng-controller="lintList">
<div class="container">
<div class="page-header">
<h1>Clippy Lints</h1>
</div>
@ -271,38 +337,62 @@ Otherwise, have a great day =^.^=
</div>
<div class="panel panel-default" ng-show="data">
<div class="panel-body row filter-panel">
<div class="col-md-6 form-inline">
<div class="form-group form-group-lg">
<p class="h4">
Lint levels
<a href="https://doc.rust-lang.org/rustc/lints/levels.html">(?)</a>
</p>
<div class="checkbox" ng-repeat="(level, enabled) in levels">
<label class="text-capitalize">
<input type="checkbox" ng-model="levels[level]" />
{{level}}
</label>
</div>
</div>
</div>
<div class="col-md-6 form-inline">
<div class="form-group form-group-lg">
<p class="h4">
Lint groups
<a href="https://github.com/rust-lang/rust-clippy/#clippy">(?)</a>
</p>
<div class="checkbox" ng-repeat="(group, enabled) in groups">
<label class="text-capitalize">
<input type="checkbox" ng-model="groups[group]" />
{{group}}
</label>
</div>
</div>
</div>
</div>
<div class="panel-body row">
<div class="col-md-12 form-horizontal">
<div class="col-12 col-md-4">
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Lint levels <span class="badge">{{selectedValuesCount(levels)}}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="checkbox">
<label ng-click="toggleLevels(true)">
<input type="checkbox" class="invisible" />
All
</label>
</li>
<li class="checkbox">
<label ng-click="toggleLevels(false)">
<input type="checkbox" class="invisible" />
None
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(level, enabled) in levels">
<label class="text-capitalize">
<input type="checkbox" ng-model="levels[level]" />
{{level}}
</label>
</li>
</ul>
</div>
<div class="btn-group" filter-dropdown>
<button type="button" class="btn btn-default dropdown-toggle">
Lint groups <span class="badge">{{selectedValuesCount(groups)}}</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="checkbox">
<label ng-click="toggleGroups(true)">
<input type="checkbox" class="invisible" />
All
</label>
</li>
<li class="checkbox">
<label ng-click="toggleGroups(false)">
<input type="checkbox" class="invisible" />
None
</label>
</li>
<li role="separator" class="divider"></li>
<li class="checkbox" ng-repeat="(group, enabled) in groups">
<label class="text-capitalize">
<input type="checkbox" ng-model="groups[group]" />
{{group}}
</label>
</li>
</ul>
</div>
</div>
<div class="col-12 col-md-8 search-control">
<div class="input-group">
<label class="input-group-addon" id="filter-label" for="filter-input">Filter:</label>
<input type="text" class="form-control" placeholder="Keywords or search string" id="filter-input" ng-model="search" ng-model-options="{debounce: 50}"/>
@ -336,7 +426,7 @@ Otherwise, have a great day =^.^=
</h2>
</header>
<div class="list-group lint-docs" ng-class="{collapse: true, in: open[lint.id]}">
<div class="list-group lint-docs" ng-if="open[lint.id]" ng-class="{collapse: true, in: open[lint.id]}">
<div class="list-group-item lint-doc-md" ng-bind-html="lint.docs | markdown"></div>
<div class="lint-additional-info-container">
<!-- Applicability -->
@ -365,7 +455,7 @@ Otherwise, have a great day =^.^=
</div>
<a href="https://github.com/rust-lang/rust-clippy">
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
<img style="position: absolute; top: 0; right: 0; border: 0; clip-path: polygon(0% 0%, 100% 0%, 100% 100%);" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on Github"/>
</a>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js"></script>
@ -429,6 +519,46 @@ Otherwise, have a great day =^.^=
);
};
})
.directive('themeDropdown', function ($document) {
return {
restrict: 'A',
link: function ($scope, $element, $attr) {
$element.bind('click', function () {
$element.toggleClass('open');
$element.addClass('open-recent');
});
$document.bind('click', function () {
if (!$element.hasClass('open-recent')) {
$element.removeClass('open');
}
$element.removeClass('open-recent');
})
}
}
})
.directive('filterDropdown', function ($document) {
return {
restrict: 'A',
link: function ($scope, $element, $attr) {
$element.bind('click', function (event) {
if (event.target.closest('button')) {
$element.toggleClass('open');
} else {
$element.addClass('open');
}
$element.addClass('open-recent');
});
$document.bind('click', function () {
if (!$element.hasClass('open-recent')) {
$element.removeClass('open');
}
$element.removeClass('open-recent');
})
}
}
})
.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
@ -462,6 +592,38 @@ Otherwise, have a great day =^.^=
suspicious: true,
};
$scope.groups = GROUPS_FILTER_DEFAULT;
const THEMES_DEFAULT = {
light: "Light",
rust: "Rust",
coal: "Coal",
navy: "Navy",
ayu: "Ayu"
};
$scope.themes = THEMES_DEFAULT;
$scope.selectTheme = function (theme) {
setTheme(theme, true);
}
$scope.toggleLevels = function (value) {
const levels = $scope.levels;
for (const key in levels) {
if (levels.hasOwnProperty(key)) {
levels[key] = value;
}
}
};
$scope.toggleGroups = function (value) {
const groups = $scope.groups;
for (const key in groups) {
if (groups.hasOwnProperty(key)) {
groups[key] = value;
}
}
};
$scope.selectedValuesCount = function (obj) {
return Object.values(obj).filter(x => x).length;
}
$scope.byGroups = function (lint) {
return $scope.groups[lint.group];
};
@ -558,28 +720,6 @@ Otherwise, have a great day =^.^=
}
}
function setupListeners() {
let themeIcon = document.getElementById("theme-icon");
let themeMenu = document.getElementById("theme-menu");
themeIcon.addEventListener("click", function(e) {
if (themeMenu.style.display == "none") {
themeMenu.style.display = "block";
} else {
themeMenu.style.display = "none";
}
});
let children = themeMenu.children;
for (let index = 0; index < children.length; index++) {
let child = children[index];
child.addEventListener("click", function(e) {
setTheme(child.id, true);
});
}
}
setupListeners();
function setTheme(theme, store) {
let enableHighlight = false;
let enableNight = false;