mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-30 05:51:58 +00:00
Merge commit 'dc5423ad448877e33cca28db2f1445c9c4473c75' into clippyup
This commit is contained in:
commit
3585e1c22f
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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" }
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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!()
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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 || {
|
||||
|
56
src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs
Normal file
56
src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
197
src/tools/clippy/clippy_lints/src/matches/needless_match.rs
Normal file
197
src/tools/clippy/clippy_lints/src/matches/needless_match.rs
Normal 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
|
||||
}
|
72
src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
Normal file
72
src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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`");
|
||||
}
|
||||
}
|
||||
|
668
src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
Normal file
668
src/tools/clippy/clippy_lints/src/only_used_in_recursion.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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 }
|
||||
|
@ -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"];
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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"]
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {}
|
38
src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs
Normal file
38
src/tools/clippy/tests/ui-internal/invalid_msrv_attr_impl.rs
Normal 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() {}
|
@ -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
|
||||
|
14
src/tools/clippy/tests/ui/allow_attributes_without_reason.rs
Normal file
14
src/tools/clippy/tests/ui/allow_attributes_without_reason.rs
Normal 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() {}
|
@ -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
|
||||
|
39
src/tools/clippy/tests/ui/cast_slice_different_sizes.rs
Normal file
39
src/tools/clippy/tests/ui/cast_slice_different_sizes.rs
Normal 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];
|
||||
}
|
52
src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr
Normal file
52
src/tools/clippy/tests/ui/cast_slice_different_sizes.stderr
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
| ^^^^^^^
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
56
src/tools/clippy/tests/ui/iter_with_drain.fixed
Normal file
56
src/tools/clippy/tests/ui/iter_with_drain.fixed
Normal 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());
|
||||
}
|
56
src/tools/clippy/tests/ui/iter_with_drain.rs
Normal file
56
src/tools/clippy/tests/ui/iter_with_drain.rs
Normal 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());
|
||||
}
|
40
src/tools/clippy/tests/ui/iter_with_drain.stderr
Normal file
40
src/tools/clippy/tests/ui/iter_with_drain.stderr
Normal 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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
28
src/tools/clippy/tests/ui/missing_spin_loop.fixed
Normal file
28
src/tools/clippy/tests/ui/missing_spin_loop.fixed
Normal 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
|
||||
}
|
28
src/tools/clippy/tests/ui/missing_spin_loop.rs
Normal file
28
src/tools/clippy/tests/ui/missing_spin_loop.rs
Normal 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
|
||||
}
|
40
src/tools/clippy/tests/ui/missing_spin_loop.stderr
Normal file
40
src/tools/clippy/tests/ui/missing_spin_loop.stderr
Normal 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
|
||||
|
23
src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed
Normal file
23
src/tools/clippy/tests/ui/missing_spin_loop_no_std.fixed
Normal 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() {}
|
23
src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs
Normal file
23
src/tools/clippy/tests/ui/missing_spin_loop_no_std.rs
Normal 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() {}
|
10
src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr
Normal file
10
src/tools/clippy/tests/ui/missing_spin_loop_no_std.stderr
Normal 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
|
||||
|
83
src/tools/clippy/tests/ui/needless_match.fixed
Normal file
83
src/tools/clippy/tests/ui/needless_match.fixed
Normal 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() {}
|
122
src/tools/clippy/tests/ui/needless_match.rs
Normal file
122
src/tools/clippy/tests/ui/needless_match.rs
Normal 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() {}
|
122
src/tools/clippy/tests/ui/needless_match.stderr
Normal file
122
src/tools/clippy/tests/ui/needless_match.stderr
Normal 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
|
||||
|
122
src/tools/clippy/tests/ui/only_used_in_recursion.rs
Normal file
122
src/tools/clippy/tests/ui/only_used_in_recursion.rs
Normal 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() {}
|
82
src/tools/clippy/tests/ui/only_used_in_recursion.stderr
Normal file
82
src/tools/clippy/tests/ui/only_used_in_recursion.stderr
Normal 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
|
||||
|
@ -1,7 +1,7 @@
|
||||
// run-rustfix
|
||||
|
||||
#![allow(unused_parens)]
|
||||
|
||||
#![allow(clippy::iter_with_drain)]
|
||||
fn f() -> usize {
|
||||
42
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
// run-rustfix
|
||||
|
||||
#![allow(unused_parens)]
|
||||
|
||||
#![allow(clippy::iter_with_drain)]
|
||||
fn f() -> usize {
|
||||
42
|
||||
}
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
23
src/tools/clippy/tests/ui/unnecessary_find_map.rs
Normal file
23
src/tools/clippy/tests/ui/unnecessary_find_map.rs
Normal 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)
|
||||
}
|
38
src/tools/clippy/tests/ui/unnecessary_find_map.stderr
Normal file
38
src/tools/clippy/tests/ui/unnecessary_find_map.stderr
Normal 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
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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">🖌</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">🖌</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;
|
||||
|
Loading…
Reference in New Issue
Block a user