id: reproducer
attributes:
label: Reproducer
- description: Please provide the code and steps to repoduce the bug
+ description: Please provide the code and steps to reproduce the bug
value: |
I tried this code:
attributes:
label: Summary
description: |
- If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occured.
+ If possible, try to provide a minimal verifiable example. You can read ["Rust Bug Minimization Patterns"][mve] for how to create smaller examples. Otherwise, provide the crate where the ICE occurred.
[mve]: http://blog.pnkfx.org/blog/2019/11/18/rust-bug-minimization-patterns/
validations:
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
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
- 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.
## Unreleased / In Rust Nightly
-[0eff589...master](https://github.com/rust-lang/rust-clippy/compare/0eff589...master)
+[57b3c4b...master](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...master)
## Rust 1.59 (beta)
-Current beta, release 2022-02-24
+Current beta, release 2022-04-07
+
+[0eff589...57b3c4b](https://github.com/rust-lang/rust-clippy/compare/0eff589...57b3c4b)
+
+### New Lints
+
+* [`single_char_lifetime_names`]
+ [#8236](https://github.com/rust-lang/rust-clippy/pull/8236)
+* [`iter_overeager_cloned`]
+ [#8203](https://github.com/rust-lang/rust-clippy/pull/8203)
+* [`transmute_undefined_repr`]
+ [#8398](https://github.com/rust-lang/rust-clippy/pull/8398)
+* [`default_union_representation`]
+ [#8289](https://github.com/rust-lang/rust-clippy/pull/8289)
+* [`manual_bits`]
+ [#8213](https://github.com/rust-lang/rust-clippy/pull/8213)
+* [`borrow_as_ptr`]
+ [#8210](https://github.com/rust-lang/rust-clippy/pull/8210)
+
+### Moves and Deprecations
+
+* Moved [`disallowed_methods`] and [`disallowed_types`] to `style` (now warn-by-default)
+ [#8261](https://github.com/rust-lang/rust-clippy/pull/8261)
+* Rename `ref_in_deref` to [`needless_borrow`]
+ [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* Moved [`mutex_atomic`] to `nursery` (now allow-by-default)
+ [#8260](https://github.com/rust-lang/rust-clippy/pull/8260)
+
+### Enhancements
+
+* [`ptr_arg`]: Now takes the argument usage into account and lints for mutable references
+ [#8271](https://github.com/rust-lang/rust-clippy/pull/8271)
+* [`unused_io_amount`]: Now supports async read and write traits
+ [#8179](https://github.com/rust-lang/rust-clippy/pull/8179)
+* [`while_let_on_iterator`]: Improved detection to catch more cases
+ [#8221](https://github.com/rust-lang/rust-clippy/pull/8221)
+* [`trait_duplication_in_bounds`]: Now covers trait functions with `Self` bounds
+ [#8252](https://github.com/rust-lang/rust-clippy/pull/8252)
+* [`unwrap_used`]: Now works for `.get(i).unwrap()` and `.get_mut(i).unwrap()`
+ [#8372](https://github.com/rust-lang/rust-clippy/pull/8372)
+* [`map_clone`]: The suggestion takes `msrv` into account
+ [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`manual_bits`] and [`borrow_as_ptr`]: Now track the `clippy::msrv` attribute
+ [#8280](https://github.com/rust-lang/rust-clippy/pull/8280)
+* [`disallowed_methods`]: Now works for methods on primitive types
+ [#8112](https://github.com/rust-lang/rust-clippy/pull/8112)
+* [`not_unsafe_ptr_arg_deref`]: Now works for type aliases
+ [#8273](https://github.com/rust-lang/rust-clippy/pull/8273)
+* [`needless_question_mark`]: Now works for async functions
+ [#8311](https://github.com/rust-lang/rust-clippy/pull/8311)
+* [`iter_not_returning_iterator`]: Now handles type projections
+ [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`wrong_self_convention`]: Now detects wrong `self` references in more cases
+ [#8208](https://github.com/rust-lang/rust-clippy/pull/8208)
+* [`single_match`]: Now works for `match` statements with tuples
+ [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+
+### False Positive Fixes
+
+* [`erasing_op`]: No longer triggers if the output type changes
+ [#8204](https://github.com/rust-lang/rust-clippy/pull/8204)
+* [`if_same_then_else`]: No longer triggers for `if let` statements
+ [#8297](https://github.com/rust-lang/rust-clippy/pull/8297)
+* [`manual_memcpy`]: No longer lints on `VecDeque`
+ [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`trait_duplication_in_bounds`]: Now takes path segments into account
+ [#8315](https://github.com/rust-lang/rust-clippy/pull/8315)
+* [`deref_addrof`]: No longer lints when the dereference or borrow occurs in different a context
+ [#8268](https://github.com/rust-lang/rust-clippy/pull/8268)
+* [`type_repetition_in_bounds`]: Now checks for full equality to prevent false positives
+ [#8224](https://github.com/rust-lang/rust-clippy/pull/8224)
+* [`ptr_arg`]: No longer lint for mutable references in traits
+ [#8369](https://github.com/rust-lang/rust-clippy/pull/8369)
+* [`implicit_clone`]: No longer lints for double references
+ [#8231](https://github.com/rust-lang/rust-clippy/pull/8231)
+* [`needless_lifetimes`]: No longer lints lifetimes for explicit `self` types
+ [#8278](https://github.com/rust-lang/rust-clippy/pull/8278)
+* [`op_ref`]: No longer lints in `BinOp` impl if that can cause recursion
+ [#8298](https://github.com/rust-lang/rust-clippy/pull/8298)
+* [`enum_variant_names`]: No longer triggers for empty variant names
+ [#8329](https://github.com/rust-lang/rust-clippy/pull/8329)
+* [`redundant_closure`]: No longer lints for `Arc<T>` or `Rc<T>`
+ [#8193](https://github.com/rust-lang/rust-clippy/pull/8193)
+* [`iter_not_returning_iterator`]: No longer lints on trait implementations but therefore on trait definitions
+ [#8228](https://github.com/rust-lang/rust-clippy/pull/8228)
+* [`single_match`]: No longer lints on exhaustive enum patterns without a wildcard
+ [#8322](https://github.com/rust-lang/rust-clippy/pull/8322)
+* [`manual_swap`]: No longer lints on cases that involve automatic dereferences
+ [#8220](https://github.com/rust-lang/rust-clippy/pull/8220)
+* [`useless_format`]: Now works for implicit named arguments
+ [#8295](https://github.com/rust-lang/rust-clippy/pull/8295)
+
+### Suggestion Fixes/Improvements
+
+* [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls
+ [#8217](https://github.com/rust-lang/rust-clippy/pull/8217)
+* [`chars_next_cmp`]: Correctly excapes the suggestion
+ [#8376](https://github.com/rust-lang/rust-clippy/pull/8376)
+* [`explicit_write`]: Add suggestions for `write!`s with format arguments
+ [#8365](https://github.com/rust-lang/rust-clippy/pull/8365)
+* [`manual_memcpy`]: Suggests `copy_from_slice` when applicable
+ [#8226](https://github.com/rust-lang/rust-clippy/pull/8226)
+* [`or_fun_call`]: Improved suggestion display for long arguments
+ [#8292](https://github.com/rust-lang/rust-clippy/pull/8292)
+* [`unnecessary_cast`]: Now correctly includes the sign
+ [#8350](https://github.com/rust-lang/rust-clippy/pull/8350)
+* [`cmp_owned`]: No longer flips the comparison order
+ [#8299](https://github.com/rust-lang/rust-clippy/pull/8299)
+* [`explicit_counter_loop`]: Now correctly suggests `iter()` on references
+ [#8382](https://github.com/rust-lang/rust-clippy/pull/8382)
+
+### ICE Fixes
+
+* [`manual_split_once`]
+ [#8250](https://github.com/rust-lang/rust-clippy/pull/8250)
+
+### Documentation Improvements
+
+* [`map_flatten`]: Add documentation for the `Option` type
+ [#8354](https://github.com/rust-lang/rust-clippy/pull/8354)
+* Document that Clippy's driver might use a different code generation than rustc
+ [#8037](https://github.com/rust-lang/rust-clippy/pull/8037)
+* Clippy's lint list will now automatically focus the search box
+ [#8343](https://github.com/rust-lang/rust-clippy/pull/8343)
+
+### Others
+
+* Clippy now warns if we find multiple Clippy config files exist
+ [#8326](https://github.com/rust-lang/rust-clippy/pull/8326)
+
+## Rust 1.59
+
+Current stable, release 2022-02-24
[e181011...0eff589](https://github.com/rust-lang/rust-clippy/compare/e181011...0eff589)
## Rust 1.58
-Current stable, released 2022-01-13
+Released 2022-01-13
[00e31fa...e181011](https://github.com/rust-lang/rust-clippy/compare/00e31fa...e181011)
<!-- 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
[`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth
[`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata
[`case_sensitive_file_extension_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#case_sensitive_file_extension_comparisons
+[`cast_enum_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_constructor
[`cast_enum_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_enum_truncation
[`cast_lossless`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless
[`cast_possible_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation
[`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
[`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
[`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
[`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
[`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
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn
[`option_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_option
[`or_fun_call`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_fun_call
+[`or_then_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#or_then_unwrap
[`out_of_bounds_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#out_of_bounds_indexing
[`overflow_check_conditional`]: https://rust-lang.github.io/rust-clippy/master/index.html#overflow_check_conditional
[`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic
[`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_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
[`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
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
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" }
bytecount = "0.6"
clap = "2.33"
indoc = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
opener = "0.5"
regex = "1.5"
shell-escape = "0.1"
cargo_metadata = "0.14"
clippy_utils = { path = "../clippy_utils" }
if_chain = "1.0"
-itertools = "0.10"
+itertools = "0.10.1"
pulldown-cmark = { version = "0.9", default-features = false }
quine-mc_cluskey = "0.2"
regex-syntax = "0.6"
"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,
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;
}
}
}
+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)
}
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)
}
fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
for ty_cause in ty_causes {
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
- if is_mutex_guard(cx, adt.did) {
+ if is_mutex_guard(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_LOCK,
},
);
}
- if is_refcell_ref(cx, adt.did) {
+ if is_refcell_ref(cx, adt.did()) {
span_lint_and_then(
cx,
AWAIT_HOLDING_REFCELL_REF,
use clippy_utils::diagnostics::span_lint_and_help;
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
+use rustc_data_structures::intern::Interned;
use rustc_hir::{Expr, ExprKind, PathSegment};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
ty::Str => {
return Some(span);
},
- ty::Adt(&ty::AdtDef { did, .. }, _) => {
+ ty::Adt(ty::AdtDef(Interned(&ty::AdtDefData { did, .. }, _)), _) => {
if ctx.tcx.is_diagnostic_item(sym::String, did) {
return Some(span);
}
--- /dev/null
+use clippy_utils::diagnostics::span_lint;
+use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{self, Ty};
+
+use super::CAST_ENUM_CONSTRUCTOR;
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>) {
+ if matches!(cast_from.kind(), ty::FnDef(..))
+ && let ExprKind::Path(path) = &cast_expr.kind
+ && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = cx.qpath_res(path, cast_expr.hir_id)
+ {
+ span_lint(
+ cx,
+ CAST_ENUM_CONSTRUCTOR,
+ expr.span,
+ "cast of an enum tuple constructor to an integer",
+ );
+ }
+}
&& let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id)
{
let i = def.variant_index_with_ctor_id(id);
- let variant = &def.variants[i];
- let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, def, i));
+ let variant = def.variant(i);
+ let nbits = utils::enum_value_nbits(get_discriminant_value(cx.tcx, *def, i));
(nbits, Some(variant))
} else {
- (utils::enum_ty_to_nbits(def, cx.tcx), None)
+ (utils::enum_ty_to_nbits(*def, cx.tcx), None)
};
let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx);
- let cast_from_ptr_size = def.repr.int.map_or(true, |ty| {
+ let cast_from_ptr_size = def.repr().int.map_or(true, |ty| {
matches!(
ty,
IntType::SignedInt(ast::IntTy::Isize) | IntType::UnsignedInt(ast::UintTy::Usize)
--- /dev/null
+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
+ }
+}
+mod cast_enum_constructor;
mod cast_lossless;
mod cast_possible_truncation;
mod cast_possible_wrap;
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;
"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"
+}
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for casts from an enum tuple constructor to an integer.
+ ///
+ /// ### Why is this bad?
+ /// The cast is easily confused with casting a c-like enum value to an integer.
+ ///
+ /// ### Example
+ /// ```rust
+ /// enum E { X(i32) };
+ /// let _ = E::X as usize;
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub CAST_ENUM_CONSTRUCTOR,
+ suspicious,
+ "casts from an enum tuple constructor to an integer"
+}
+
pub struct Casts {
msrv: Option<RustcVersion>,
}
CAST_LOSSLESS,
CAST_REF_TO_MUT,
CAST_PTR_ALIGNMENT,
+ CAST_SLICE_DIFFERENT_SIZES,
UNNECESSARY_CAST,
FN_TO_NUMERIC_CAST_ANY,
FN_TO_NUMERIC_CAST,
CHAR_LIT_AS_U8,
PTR_AS_PTR,
CAST_ENUM_TRUNCATION,
+ CAST_ENUM_CONSTRUCTOR
]);
impl<'tcx> LateLintPass<'tcx> for Casts {
cast_sign_loss::check(cx, expr, cast_expr, cast_from, cast_to);
}
cast_lossless::check(cx, expr, cast_expr, cast_from, cast_to, &self.msrv);
+ cast_enum_constructor::check(cx, expr, cast_expr, cast_from);
}
}
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);
.into()
}
-pub(super) fn enum_ty_to_nbits(adt: &AdtDef, tcx: TyCtxt<'_>) -> u64 {
+pub(super) fn enum_ty_to_nbits(adt: AdtDef<'_>, tcx: TyCtxt<'_>) -> u64 {
let mut explicit = 0i128;
let (start, end) = adt
- .variants
+ .variants()
.iter()
.fold((0, i128::MIN), |(start, end), variant| match variant.discr {
VariantDiscr::Relative(x) => match explicit.checked_add(i128::from(x)) {
///
/// Should be written:
///
- /// ```rust.ignore
+ /// ```rust,ignore
/// if x && y {
/// …
/// }
///
/// Should be written:
///
- /// ```rust.ignore
+ /// ```rust,ignore
/// if x {
/// …
/// } else if y {
};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{Block, Expr, ExprKind, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext};
/// Duplicate code is less maintainable.
///
/// ### Known problems
- /// * The lint doesn't check if the moved expressions modify values that are beeing used in
+ /// * The lint doesn't check if the moved expressions modify values that are being used in
/// the if condition. The suggestion can in that case modify the behavior of the program.
/// See [rust-clippy#7452](https://github.com/rust-lang/rust-clippy/issues/7452)
///
add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit();
}
- let add_optional_msgs = |diag: &mut DiagnosticBuilder<'_>| {
+ let add_optional_msgs = |diag: &mut Diagnostic| {
if add_expr_note {
diag.note("The end suggestion probably needs some adjustments to use the expression result correctly");
}
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;
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!()
then {
// TODO: Work out a way to put "whatever the imported way of referencing
// this type in this file" rather than a fully-qualified type.
- let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did));
+ let replacement = format!("{}::default()", cx.tcx.def_path_str(def.did()));
span_lint_and_sugg(
cx,
DEFAULT_TRAIT_ACCESS,
if let Some(adt) = binding_type.ty_adt_def();
if adt.is_struct();
let variant = adt.non_enum_variant();
- if adt.did.is_local() || !variant.is_field_list_non_exhaustive();
+ if adt.did().is_local() || !variant.is_field_list_non_exhaustive();
let module_did = cx.tcx.parent_module(stmt.hir_id).to_def_id();
if variant
.fields
if let ty::Adt(adt_def, substs) = binding_type.kind();
if !substs.is_empty();
then {
- let adt_def_ty_name = cx.tcx.item_name(adt_def.did);
+ let adt_def_ty_name = cx.tcx.item_name(adt_def.did());
let generic_args = substs.iter().collect::<Vec<_>>();
let tys_str = generic_args
.iter()
if_chain! {
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
- if let Some(variant) = adt_def.variants.iter().next();
+ if let Some(variant) = adt_def.variants().iter().next();
then {
let fields_def = &variant.fields;
_ => false,
};
if should_emit {
- let path_string = cx.tcx.def_path_str(adt_def.did);
+ let path_string = cx.tcx.def_path_str(adt_def.did());
span_lint_and_help(
cx,
DERIVABLE_IMPLS,
let has_copy_impl = cx.tcx.all_local_trait_impls(()).get(©_id).map_or(false, |impls| {
impls
.iter()
- .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did == adt.did))
+ .any(|&id| matches!(cx.tcx.type_of(id).kind(), ty::Adt(adt, _) if ty_adt.did() == adt.did()))
});
if !has_copy_impl {
return;
if let Some(trait_def_id) = trait_ref.trait_def_id();
if match_def_path(cx, trait_def_id, &paths::SERDE_DESERIALIZE);
if let ty::Adt(def, _) = ty.kind();
- if let Some(local_def_id) = def.did.as_local();
+ if let Some(local_def_id) = def.did().as_local();
let adt_hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_def_id);
if !is_lint_allowed(cx, UNSAFE_DERIVE_DESERIALIZE, adt_hir_id);
- if cx.tcx.inherent_impls(def.did)
+ if cx.tcx.inherent_impls(def.did())
.iter()
.map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local()))
.any(|imp| has_unsafe(cx, imp));
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
Ok(p) => p,
Err(errs) => {
- for mut err in errs {
- err.cancel();
- }
+ drop(errs);
return false;
},
};
loop {
match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => match &item.kind {
- // Tests with one of these items are ignored
- ItemKind::Static(..)
- | ItemKind::Const(..)
- | ItemKind::ExternCrate(..)
- | ItemKind::ForeignMod(..) => return false,
- // We found a main function ...
ItemKind::Fn(box Fn {
sig, body: Some(block), ..
}) if item.ident.name == sym::main => {
return false;
}
},
- // Another function was found; this case is ignored too
- ItemKind::Fn(..) => return false,
+ // Tests with one of these items are ignored
+ ItemKind::Static(..)
+ | ItemKind::Const(..)
+ | ItemKind::ExternCrate(..)
+ | ItemKind::ForeignMod(..)
+ // Another function was found; this case is ignored
+ | ItemKind::Fn(..) => return false,
_ => {},
},
Ok(None) => break,
- Err(mut e) => {
+ Err(e) => {
e.cancel();
return false;
},
if let ItemKind::Enum(..) = item.kind {
let ty = cx.tcx.type_of(item.def_id);
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
- if adt.variants.is_empty() {
+ if adt.variants().is_empty() {
span_lint_and_help(
cx,
EMPTY_ENUM,
if let Some(Constant::Int(val)) = constant.and_then(miri_to_const) {
if let ty::Adt(adt, _) = ty.kind() {
if adt.is_enum() {
- ty = adt.repr.discr_type().to_ty(cx.tcx);
+ ty = adt.repr().discr_type().to_ty(cx.tcx);
}
}
match ty.kind() {
fn are_equal<'tcx>(cx: &LateContext<'tcx>, middle_ty: Ty<'_>, hir_ty: &rustc_hir::Ty<'_>) -> bool {
if_chain! {
if let ty::Adt(adt_def, _) = middle_ty.kind();
- if let Some(local_did) = adt_def.did.as_local();
+ if let Some(local_did) = adt_def.did().as_local();
let item = cx.tcx.hir().expect_item(local_did);
let middle_ty_id = item.def_id.to_def_id();
if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind;
ty::ImplContainer(def_id) => {
let ty = cx.tcx.type_of(def_id);
match ty.kind() {
- ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did),
+ ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
_ => ty.to_string(),
}
},
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
- let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
- if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
- || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
{
self.result.push(expr.span);
}
///
/// ### Known problems
/// If the user can ensure that b is larger than a, the `.abs()` is
- /// technically unneccessary. However, it will make the code more robust and doesn't have any
+ /// technically unnecessary. However, it will make the code more robust and doesn't have any
/// large performance implications. If the abs call was deliberately left out for performance
/// reasons, it is probably better to state this explicitly in the code, which then can be done
/// with an allow.
if_chain! {
- // left hand side is a substraction
+ // left hand side is a subtraction
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub,
if let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id);
if match_def_path(cx, def_id, &paths::F32_EPSILON) || match_def_path(cx, def_id, &paths::F64_EPSILON);
- // values of the substractions on the left hand side are of the type float
+ // values of the subtractions on the left hand side are of the type float
let t_val_l = cx.typeck_results().expr_ty(val_l);
let t_val_r = cx.typeck_results().expr_ty(val_r);
if let ty::Float(_) = t_val_l.kind();
if_chain! {
if format_args.format_string_parts == [kw::Empty];
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
- ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did),
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(sym::String, adt.did()),
ty::Str => true,
_ => false,
};
// primitive types are never mutable
ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::Float(_) | ty::Str => false,
ty::Adt(adt, substs) => {
- tys.insert(adt.did) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
- || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did, path))
+ tys.insert(adt.did()) && !ty.is_freeze(cx.tcx.at(span), cx.param_env)
+ || KNOWN_WRAPPER_TYS.iter().any(|path| match_def_path(cx, adt.did(), path))
&& substs.types().any(|ty| is_mutable_ty(cx, ty, span, tys))
},
ty::Tuple(substs) => substs.iter().any(|ty| is_mutable_ty(cx, ty, span, tys)),
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
-use clippy_utils::consts::{constant_simple, Constant};
+use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{clip, unsext};
check(cx, left, -1, e.span, right.span);
check(cx, right, -1, e.span, left.span);
},
+ BinOpKind::Rem => check_remainder(cx, left, right, e.span, left.span),
_ => (),
}
}
&& constant_simple(cx, cx.typeck_results(), left) == Some(Constant::Int(1)))
}
+fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
+ let lhs_const = constant_full_int(cx, cx.typeck_results(), left);
+ let rhs_const = constant_full_int(cx, cx.typeck_results(), right);
+ if match (lhs_const, rhs_const) {
+ (Some(FullInt::S(lv)), Some(FullInt::S(rv))) => lv.abs() < rv.abs(),
+ (Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
+ _ => return,
+ } {
+ span_ineffective_operation(cx, span, arg);
+ }
+}
+
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
1 => v == 1,
_ => unreachable!(),
} {
- span_lint(
- cx,
- IDENTITY_OP,
- span,
- &format!(
- "the operation is ineffective. Consider reducing it to `{}`",
- snippet(cx, arg, "..")
- ),
- );
+ span_ineffective_operation(cx, span, arg);
}
}
}
+
+fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span) {
+ span_lint(
+ cx,
+ IDENTITY_OP,
+ span,
+ &format!(
+ "the operation is ineffective. Consider reducing it to `{}`",
+ snippet(cx, arg, "..")
+ ),
+ );
+}
use std::borrow::Cow;
use std::collections::BTreeMap;
-use rustc_errors::DiagnosticBuilder;
+use rustc_errors::Diagnostic;
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_inf, walk_ty, Visitor};
use rustc_hir::{Body, Expr, ExprKind, GenericArg, Item, ItemKind, QPath, TyKind};
fn suggestion<'tcx>(
cx: &LateContext<'tcx>,
- diag: &mut DiagnosticBuilder<'_>,
+ diag: &mut Diagnostic,
generics_span: Span,
generics_suggestion_span: Span,
target: &ImplicitHasherType<'_>,
let ty = cx.typeck_results().expr_ty(expr);
if let Some(adt_def) = ty.ty_adt_def();
if adt_def.is_struct();
- if let Some(variant) = adt_def.variants.iter().next();
+ if let Some(variant) = adt_def.variants().iter().next();
if fields.iter().all(|f| f.is_shorthand);
then {
let mut def_order_map = FxHashMap::default();
//! checks for `#[inline]` on trait methods without bodies
use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::sugg::DiagnosticBuilderExt;
+use clippy_utils::sugg::DiagnosticExt;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::{TraitFn, TraitItem, TraitItemKind};
if let ItemKind::Enum(ref def, _) = item.kind {
let ty = cx.tcx.type_of(item.def_id);
let adt = ty.ty_adt_def().expect("already checked whether this is an enum");
- if adt.variants.len() <= 1 {
+ if adt.variants().len() <= 1 {
return;
}
let mut variants_size: Vec<VariantInfo> = Vec::new();
- for (i, variant) in adt.variants.iter().enumerate() {
+ for (i, variant) in adt.variants().iter().enumerate() {
let mut fields_size = Vec::new();
for (i, f) in variant.fields.iter().enumerate() {
let ty = cx.tcx.type_of(f.did);
fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
- ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did) => {
- subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did))
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
+ subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did()))
},
- ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did) => subs
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => subs
.type_at(0)
.is_integral()
- .then(|| LenOutput::Result(adt.did, subs.type_at(1))),
+ .then(|| LenOutput::Result(adt.did(), subs.type_at(1))),
_ => None,
}
}
fn matches_is_empty_output(self, ty: Ty<'_>) -> bool {
match (self, ty.kind()) {
(_, &ty::Bool) => true,
- (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did => subs.type_at(0).is_bool(),
- (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did => {
+ (Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
+ (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did() => {
subs.type_at(0).is_bool() && subs.type_at(1) == err_ty
},
_ => false,
.any(|item| is_is_empty(cx, item))
}),
ty::Projection(ref proj) => has_is_empty_impl(cx, proj.item_def_id),
- ty::Adt(id, _) => has_is_empty_impl(cx, id.did),
+ ty::Adt(id, _) => has_is_empty_impl(cx, id.did()),
ty::Array(..) | ty::Slice(..) | ty::Str => true,
_ => false,
}
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
+ LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
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),
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),
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),
LintId::of(methods::OPTION_FILTER_MAP),
LintId::of(methods::OPTION_MAP_OR_NONE),
LintId::of(methods::OR_FUN_CALL),
+ LintId::of(methods::OR_THEN_UNWRAP),
LintId::of(methods::RESULT_MAP_OR_INTO_OPTION),
LintId::of(methods::SEARCH_IS_SOME),
LintId::of(methods::SHOULD_IMPLEMENT_TRAIT),
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),
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),
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
- LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
- LintId::of(try_err::TRY_ERR),
LintId::of(types::BORROWED_BOX),
LintId::of(types::BOX_COLLECTION),
LintId::of(types::REDUNDANT_ALLOCATION),
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),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP),
+ LintId::of(methods::OR_THEN_UNWRAP),
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),
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),
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),
LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(swap::ALMOST_SWAPPED),
- LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
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),
#[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,
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,
cargo::REDUNDANT_FEATURE_NAMES,
cargo::WILDCARD_DEPENDENCIES,
case_sensitive_file_extension_comparisons::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS,
+ casts::CAST_ENUM_CONSTRUCTOR,
casts::CAST_ENUM_TRUNCATION,
casts::CAST_LOSSLESS,
casts::CAST_POSSIBLE_TRUNCATION,
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,
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,
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,
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,
methods::OPTION_FILTER_MAP,
methods::OPTION_MAP_OR_NONE,
methods::OR_FUN_CALL,
+ methods::OR_THEN_UNWRAP,
methods::RESULT_MAP_OR_INTO_OPTION,
methods::SEARCH_IS_SOME,
methods::SHOULD_IMPLEMENT_TRAIT,
methods::SUSPICIOUS_SPLITN,
methods::UNINIT_ASSUMED_INIT,
methods::UNNECESSARY_FILTER_MAP,
+ methods::UNNECESSARY_FIND_MAP,
methods::UNNECESSARY_FOLD,
+ methods::UNNECESSARY_JOIN,
methods::UNNECESSARY_LAZY_EVALUATIONS,
methods::UNNECESSARY_TO_OWNED,
methods::UNWRAP_OR_ELSE_DEFAULT,
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,
LintId::of(future_not_send::FUTURE_NOT_SEND),
LintId::of(index_refutable_slice::INDEX_REFUTABLE_SLICE),
LintId::of(let_if_seq::USELESS_LET_IF_SEQ),
+ LintId::of(methods::ITER_WITH_DRAIN),
LintId::of(missing_const_for_fn::MISSING_CONST_FOR_FN),
LintId::of(mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),
LintId::of(mutex_atomic::MUTEX_ATOMIC),
LintId::of(strings::STRING_LIT_AS_BYTES),
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
+ LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(use_self::USE_SELF),
])
LintId::of(methods::IMPLICIT_CLONE),
LintId::of(methods::INEFFICIENT_TO_STRING),
LintId::of(methods::MAP_UNWRAP_OR),
+ LintId::of(methods::UNNECESSARY_JOIN),
LintId::of(misc::FLOAT_CMP),
LintId::of(misc::USED_UNDERSCORE_BINDING),
LintId::of(mut_mut::MUT_MUT),
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(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),
LintId::of(strings::STRING_SLICE),
LintId::of(strings::STRING_TO_STRING),
LintId::of(strings::STR_TO_STRING),
+ LintId::of(try_err::TRY_ERR),
LintId::of(types::RC_BUFFER),
LintId::of(types::RC_MUTEX),
LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
- LintId::of(try_err::TRY_ERR),
LintId::of(unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME),
LintId::of(unused_unit::UNUSED_UNIT),
LintId::of(upper_case_acronyms::UPPER_CASE_ACRONYMS),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_LOCK),
LintId::of(await_holding_invalid::AWAIT_HOLDING_REFCELL_REF),
+ LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
LintId::of(casts::CAST_ENUM_TRUNCATION),
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
// FIXME: switch to something more ergonomic here, once available.
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
+extern crate rustc_arena;
extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_attr;
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;
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));
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 || {
fn get_slice_like_element_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
match ty.kind() {
- ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did) => Some(subs.type_at(0)),
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Vec, adt.did()) => Some(subs.type_at(0)),
ty::Ref(_, subty, _) => get_slice_like_element_ty(cx, *subty),
ty::Slice(ty) | ty::Array(ty, _) => Some(*ty),
_ => None,
--- /dev/null
+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
+ );
+ }
+ }
+}
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;
"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,
WHILE_IMMUTABLE_CONDITION,
SAME_ITEM_PUSH,
SINGLE_ELEMENT_LOOP,
+ MISSING_SPIN_LOOP,
]);
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);
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::{path_to_local, search_same, SpanlessEq, SpanlessHash};
-use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdSet, Pat, PatKind};
+use core::cmp::Ordering;
+use core::iter;
+use core::slice;
+use rustc_arena::DroplessArena;
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdSet, Pat, PatKind, RangeEnd};
use rustc_lint::LateContext;
+use rustc_middle::ty;
+use rustc_span::Symbol;
use std::collections::hash_map::Entry;
use super::MATCH_SAME_ARMS;
-pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
+#[allow(clippy::too_many_lines)]
+pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
let hash = |&(_, arm): &(usize, &Arm<'_>)| -> u64 {
let mut h = SpanlessHash::new(cx);
h.hash_expr(arm.body);
h.finish()
};
+ let arena = DroplessArena::default();
+ let normalized_pats: Vec<_> = arms
+ .iter()
+ .map(|a| NormalizedPat::from_pat(cx, &arena, a.pat))
+ .collect();
+
+ // The furthast forwards a pattern can move without semantic changes
+ let forwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[i + 1..]
+ .iter()
+ .enumerate()
+ .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j))
+ .unwrap_or(normalized_pats.len())
+ })
+ .collect();
+
+ // The furthast backwards a pattern can move without semantic changes
+ let backwards_blocking_idxs: Vec<_> = normalized_pats
+ .iter()
+ .enumerate()
+ .map(|(i, pat)| {
+ normalized_pats[..i]
+ .iter()
+ .enumerate()
+ .rev()
+ .zip(forwards_blocking_idxs[..i].iter().copied().rev())
+ .skip_while(|&(_, forward_block)| forward_block > i)
+ .find_map(|((j, other), forward_block)| {
+ (forward_block == i || pat.has_overlapping_values(other)).then(|| j)
+ })
+ .unwrap_or(0)
+ })
+ .collect();
+
let eq = |&(lindex, lhs): &(usize, &Arm<'_>), &(rindex, rhs): &(usize, &Arm<'_>)| -> bool {
let min_index = usize::min(lindex, rindex);
let max_index = usize::max(lindex, rindex);
}
};
// Arms with a guard are ignored, those can’t always be merged together
- // This is also the case for arms in-between each there is an arm with a guard
- (min_index..=max_index).all(|index| arms[index].guard.is_none())
- && SpanlessEq::new(cx)
- .expr_fallback(eq_fallback)
- .eq_expr(lhs.body, rhs.body)
- // these checks could be removed to allow unused bindings
- && bindings_eq(lhs.pat, local_map.keys().copied().collect())
- && bindings_eq(rhs.pat, local_map.values().copied().collect())
+ // If both arms overlap with an arm in between then these can't be merged either.
+ !(backwards_blocking_idxs[max_index] > min_index && forwards_blocking_idxs[min_index] < max_index)
+ && lhs.guard.is_none()
+ && rhs.guard.is_none()
+ && SpanlessEq::new(cx)
+ .expr_fallback(eq_fallback)
+ .eq_expr(lhs.body, rhs.body)
+ // these checks could be removed to allow unused bindings
+ && bindings_eq(lhs.pat, local_map.keys().copied().collect())
+ && bindings_eq(rhs.pat, local_map.values().copied().collect())
};
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
- for (&(_, i), &(_, j)) in search_same(&indexed_arms, hash, eq) {
- span_lint_and_then(
- cx,
- MATCH_SAME_ARMS,
- j.body.span,
- "this `match` has identical arm bodies",
- |diag| {
- diag.span_note(i.body.span, "same as this");
-
- // Note: this does not use `span_suggestion` on purpose:
- // there is no clean way
- // to remove the other arm. Building a span and suggest to replace it to ""
- // makes an even more confusing error message. Also in order not to make up a
- // span for the whole pattern, the suggestion is only shown when there is only
- // one pattern. The user should know about `|` if they are already using it…
-
- let lhs = snippet(cx, i.pat.span, "<pat1>");
- let rhs = snippet(cx, j.pat.span, "<pat2>");
-
- if let PatKind::Wild = j.pat.kind {
- // if the last arm is _, then i could be integrated into _
- // note that i.pat cannot be _, because that would mean that we're
- // hiding all the subsequent arms, and rust won't compile
- diag.span_note(
- i.body.span,
- &format!(
- "`{}` has the same arm body as the `_` wildcard, consider removing it",
- lhs
- ),
- );
+ for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
+ if matches!(arm2.pat.kind, PatKind::Wild) {
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ arm1.span,
+ "this match arm has an identical body to the `_` wildcard arm",
+ |diag| {
+ diag.span_suggestion(
+ arm1.span,
+ "try removing the arm",
+ String::new(),
+ Applicability::MaybeIncorrect,
+ )
+ .help("or try changing either arm body")
+ .span_note(arm2.span, "`_` wildcard arm here");
+ },
+ );
+ } else {
+ let back_block = backwards_blocking_idxs[j];
+ let (keep_arm, move_arm) = if back_block < i || (back_block == 0 && forwards_blocking_idxs[i] <= j) {
+ (arm1, arm2)
+ } else {
+ (arm2, arm1)
+ };
+
+ span_lint_and_then(
+ cx,
+ MATCH_SAME_ARMS,
+ keep_arm.span,
+ "this match arm has an identical body to another arm",
+ |diag| {
+ let move_pat_snip = snippet(cx, move_arm.pat.span, "<pat2>");
+ let keep_pat_snip = snippet(cx, keep_arm.pat.span, "<pat1>");
+
+ diag.span_suggestion(
+ keep_arm.pat.span,
+ "try merging the arm patterns",
+ format!("{} | {}", keep_pat_snip, move_pat_snip),
+ Applicability::MaybeIncorrect,
+ )
+ .help("or try changing either arm body")
+ .span_note(move_arm.span, "other arm here");
+ },
+ );
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum NormalizedPat<'a> {
+ Wild,
+ Struct(Option<DefId>, &'a [(Symbol, Self)]),
+ Tuple(Option<DefId>, &'a [Self]),
+ Or(&'a [Self]),
+ Path(Option<DefId>),
+ LitStr(Symbol),
+ LitBytes(&'a [u8]),
+ LitInt(u128),
+ LitBool(bool),
+ Range(PatRange),
+ /// A slice pattern. If the second value is `None`, then this matches an exact size. Otherwise
+ /// the first value contains everything before the `..` wildcard pattern, and the second value
+ /// contains everything afterwards. Note that either side, or both sides, may contain zero
+ /// patterns.
+ Slice(&'a [Self], Option<&'a [Self]>),
+}
+
+#[derive(Clone, Copy)]
+struct PatRange {
+ start: u128,
+ end: u128,
+ bounds: RangeEnd,
+}
+impl PatRange {
+ fn contains(&self, x: u128) -> bool {
+ x >= self.start
+ && match self.bounds {
+ RangeEnd::Included => x <= self.end,
+ RangeEnd::Excluded => x < self.end,
+ }
+ }
+
+ fn overlaps(&self, other: &Self) -> bool {
+ // Note: Empty ranges are impossible, so this is correct even though it would return true if an
+ // empty exclusive range were to reside within an inclusive range.
+ (match self.bounds {
+ RangeEnd::Included => self.end >= other.start,
+ RangeEnd::Excluded => self.end > other.start,
+ } && match other.bounds {
+ RangeEnd::Included => self.start <= other.end,
+ RangeEnd::Excluded => self.start < other.end,
+ })
+ }
+}
+
+/// Iterates over the pairs of fields with matching names.
+fn iter_matching_struct_fields<'a>(
+ left: &'a [(Symbol, NormalizedPat<'a>)],
+ right: &'a [(Symbol, NormalizedPat<'a>)],
+) -> impl Iterator<Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>)> + 'a {
+ struct Iter<'a>(
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ slice::Iter<'a, (Symbol, NormalizedPat<'a>)>,
+ );
+ impl<'a> Iterator for Iter<'a> {
+ type Item = (&'a NormalizedPat<'a>, &'a NormalizedPat<'a>);
+ fn next(&mut self) -> Option<Self::Item> {
+ // Note: all the fields in each slice are sorted by symbol value.
+ let mut left = self.0.next()?;
+ let mut right = self.1.next()?;
+ loop {
+ match left.0.cmp(&right.0) {
+ Ordering::Equal => return Some((&left.1, &right.1)),
+ Ordering::Less => left = self.0.next()?,
+ Ordering::Greater => right = self.1.next()?,
+ }
+ }
+ }
+ }
+ Iter(left.iter(), right.iter())
+}
+
+#[allow(clippy::similar_names)]
+impl<'a> NormalizedPat<'a> {
+ #[allow(clippy::too_many_lines)]
+ fn from_pat(cx: &LateContext<'_>, arena: &'a DroplessArena, pat: &'a Pat<'_>) -> Self {
+ match pat.kind {
+ PatKind::Wild | PatKind::Binding(.., None) => Self::Wild,
+ PatKind::Binding(.., Some(pat)) | PatKind::Box(pat) | PatKind::Ref(pat, _) => {
+ Self::from_pat(cx, arena, pat)
+ },
+ PatKind::Struct(ref path, fields, _) => {
+ let fields =
+ arena.alloc_from_iter(fields.iter().map(|f| (f.ident.name, Self::from_pat(cx, arena, f.pat))));
+ fields.sort_by_key(|&(name, _)| name);
+ Self::Struct(cx.qpath_res(path, pat.hir_id).opt_def_id(), fields)
+ },
+ PatKind::TupleStruct(ref path, pats, wild_idx) => {
+ let adt = match cx.typeck_results().pat_ty(pat).ty_adt_def() {
+ Some(x) => x,
+ None => return Self::Wild,
+ };
+ let (var_id, variant) = if adt.is_enum() {
+ match cx.qpath_res(path, pat.hir_id).opt_def_id() {
+ Some(x) => (Some(x), adt.variant_with_ctor_id(x)),
+ None => return Self::Wild,
+ }
} else {
- diag.span_help(i.pat.span, &format!("consider refactoring into `{} | {}`", lhs, rhs,))
- .help("...or consider changing the match arm bodies");
+ (None, adt.non_enum_variant())
+ };
+ let (front, back) = match wild_idx {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(variant.fields.len() - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(var_id, pats)
+ },
+ PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()),
+ PatKind::Tuple(pats, wild_idx) => {
+ let field_count = match cx.typeck_results().pat_ty(pat).kind() {
+ ty::Tuple(subs) => subs.len(),
+ _ => return Self::Wild,
+ };
+ let (front, back) = match wild_idx {
+ Some(i) => pats.split_at(i),
+ None => (pats, [].as_slice()),
+ };
+ let pats = arena.alloc_from_iter(
+ front
+ .iter()
+ .map(|pat| Self::from_pat(cx, arena, pat))
+ .chain(iter::repeat_with(|| Self::Wild).take(field_count - pats.len()))
+ .chain(back.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ );
+ Self::Tuple(None, pats)
+ },
+ PatKind::Lit(e) => match &e.kind {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Str(sym, _) => Self::LitStr(sym),
+ LitKind::ByteStr(ref bytes) => Self::LitBytes(&**bytes),
+ LitKind::Byte(val) => Self::LitInt(val.into()),
+ LitKind::Char(val) => Self::LitInt(val.into()),
+ LitKind::Int(val, _) => Self::LitInt(val),
+ LitKind::Bool(val) => Self::LitBool(val),
+ LitKind::Float(..) | LitKind::Err(_) => Self::Wild,
+ },
+ _ => Self::Wild,
+ },
+ PatKind::Range(start, end, bounds) => {
+ // TODO: Handle negative integers. They're currently treated as a wild match.
+ let start = match start {
+ None => 0,
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => val,
+ LitKind::Char(val) => val.into(),
+ LitKind::Byte(val) => val.into(),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ let (end, bounds) = match end {
+ None => (u128::MAX, RangeEnd::Included),
+ Some(e) => match &e.kind {
+ ExprKind::Lit(lit) => match lit.node {
+ LitKind::Int(val, _) => (val, bounds),
+ LitKind::Char(val) => (val.into(), bounds),
+ LitKind::Byte(val) => (val.into(), bounds),
+ _ => return Self::Wild,
+ },
+ _ => return Self::Wild,
+ },
+ };
+ Self::Range(PatRange { start, end, bounds })
+ },
+ PatKind::Slice(front, wild_pat, back) => Self::Slice(
+ arena.alloc_from_iter(front.iter().map(|pat| Self::from_pat(cx, arena, pat))),
+ wild_pat.map(|_| &*arena.alloc_from_iter(back.iter().map(|pat| Self::from_pat(cx, arena, pat)))),
+ ),
+ }
+ }
+
+ /// Checks if two patterns overlap in the values they can match assuming they are for the same
+ /// type.
+ fn has_overlapping_values(&self, other: &Self) -> bool {
+ match (*self, *other) {
+ (Self::Wild, _) | (_, Self::Wild) => true,
+ (Self::Or(pats), ref other) | (ref other, Self::Or(pats)) => {
+ pats.iter().any(|pat| pat.has_overlapping_values(other))
+ },
+ (Self::Struct(lpath, lfields), Self::Struct(rpath, rfields)) => {
+ if lpath != rpath {
+ return false;
+ }
+ iter_matching_struct_fields(lfields, rfields).all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Tuple(lpath, lpats), Self::Tuple(rpath, rpats)) => {
+ if lpath != rpath {
+ return false;
}
+ lpats
+ .iter()
+ .zip(rpats.iter())
+ .all(|(lpat, rpat)| lpat.has_overlapping_values(rpat))
+ },
+ (Self::Path(x), Self::Path(y)) => x == y,
+ (Self::LitStr(x), Self::LitStr(y)) => x == y,
+ (Self::LitBytes(x), Self::LitBytes(y)) => x == y,
+ (Self::LitInt(x), Self::LitInt(y)) => x == y,
+ (Self::LitBool(x), Self::LitBool(y)) => x == y,
+ (Self::Range(ref x), Self::Range(ref y)) => x.overlaps(y),
+ (Self::Range(ref range), Self::LitInt(x)) | (Self::LitInt(x), Self::Range(ref range)) => range.contains(x),
+ (Self::Slice(lpats, None), Self::Slice(rpats, None)) => {
+ lpats.len() == rpats.len() && lpats.iter().zip(rpats.iter()).all(|(x, y)| x.has_overlapping_values(y))
},
- );
+ (Self::Slice(pats, None), Self::Slice(front, Some(back)))
+ | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => {
+ // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater
+ // then the minium length required will be greater than the length of `pats`.
+ if pats.len() < front.len() + back.len() {
+ return false;
+ }
+ pats[..front.len()]
+ .iter()
+ .zip(front.iter())
+ .chain(pats[pats.len() - back.len()..].iter().zip(back.iter()))
+ .all(|(x, y)| x.has_overlapping_values(y))
+ },
+ (Self::Slice(lfront, Some(lback)), Self::Slice(rfront, Some(rback))) => lfront
+ .iter()
+ .zip(rfront.iter())
+ .chain(lback.iter().rev().zip(rback.iter().rev()))
+ .all(|(x, y)| x.has_overlapping_values(y)),
+
+ // Enums can mix unit variants with tuple/struct variants. These can never overlap.
+ (Self::Path(_), Self::Tuple(..) | Self::Struct(..))
+ | (Self::Tuple(..) | Self::Struct(..), Self::Path(_)) => false,
+
+ // Tuples can be matched like a struct.
+ (Self::Tuple(x, _), Self::Struct(y, _)) | (Self::Struct(x, _), Self::Tuple(y, _)) => {
+ // TODO: check fields here.
+ x == y
+ },
+
+ // TODO: Lit* with Path, Range with Path, LitBytes with Slice
+ _ => true,
+ }
}
}
// Accumulate the variants which should be put in place of the wildcard because they're not
// already covered.
- let has_hidden = adt_def.variants.iter().any(|x| is_hidden(cx, x));
- let mut missing_variants: Vec<_> = adt_def.variants.iter().filter(|x| !is_hidden(cx, x)).collect();
+ let has_hidden = adt_def.variants().iter().any(|x| is_hidden(cx, x));
+ let mut missing_variants: Vec<_> = adt_def.variants().iter().filter(|x| !is_hidden(cx, x)).collect();
let mut path_prefix = CommonPrefixSearcher::None;
for arm in arms {
}
s
} else {
- let mut s = cx.tcx.def_path_str(adt_def.did);
+ let mut s = cx.tcx.def_path_str(adt_def.did());
s.push_str("::");
s
},
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;
"`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>,
REDUNDANT_PATTERN_MATCHING,
MATCH_LIKE_MATCHES_MACRO,
MATCH_SAME_ARMS,
+ NEEDLESS_MATCH,
]);
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;
match_like_matches::check(cx, expr);
}
redundant_pattern_match::check(cx, expr);
+ needless_match::check(cx, expr);
}
}
--- /dev/null
+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
+}
if_chain! {
if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def();
if let Ok(vid) = cx.tcx.lang_items().require(Self::VARIANT_LANG_ITEM);
- if Some(adt.did) == cx.tcx.parent(vid);
+ if Some(adt.did()) == cx.tcx.parent(vid);
then {} else { return false; }
}
let inner_ty = match recv_ty.kind() {
// `Option<T>` -> `T`
ty::Adt(adt, subst)
- if cx.tcx.is_diagnostic_item(sym::Option, adt.did) && meets_msrv(msrv, &msrvs::OPTION_COPIED) =>
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && meets_msrv(msrv, &msrvs::OPTION_COPIED) =>
{
subst.type_at(0)
},
if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind;
if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind;
if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def();
- if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did) {
+ if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) {
Some(false)
- } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did) {
+ } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) {
Some(true)
} else {
None
let return_type = cx.typeck_results().expr_ty(expr);
let input_type = cx.typeck_results().expr_ty(recv);
let (input_type, ref_count) = peel_mid_ty_refs(input_type);
- if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did));
+ if let Some(ty_name) = input_type.ty_adt_def().map(|adt_def| cx.tcx.item_name(adt_def.did()));
if return_type == input_type;
then {
let mut app = Applicability::MachineApplicable;
}
if let ty::Adt(adt, substs) = ty.kind() {
- match_def_path(cx, adt.did, &paths::COW) && substs.type_at(1).is_str()
+ match_def_path(cx, adt.did(), &paths::COW) && substs.type_at(1).is_str()
} else {
false
}
--- /dev/null
+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,
+ );
+ }
+ }
+ }
+}
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_sugg_for_edges;
use clippy_utils::is_trait_method;
-use clippy_utils::source::snippet;
+use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
-use rustc_hir as hir;
+use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty;
-use rustc_span::symbol::sym;
+use rustc_span::{symbol::sym, Span};
use super::MAP_FLATTEN;
/// lint use of `map().flatten()` for `Iterators` and 'Options'
-pub(super) fn check<'tcx>(
- cx: &LateContext<'tcx>,
- expr: &'tcx hir::Expr<'_>,
- recv: &'tcx hir::Expr<'_>,
- map_arg: &'tcx hir::Expr<'_>,
-) {
- // lint if caller of `.map().flatten()` is an Iterator
- if is_trait_method(cx, expr, sym::Iterator) {
- let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
- let is_map_to_option = match map_closure_ty.kind() {
- ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
- let map_closure_sig = match map_closure_ty.kind() {
- ty::Closure(_, substs) => substs.as_closure().sig(),
- _ => map_closure_ty.fn_sig(cx.tcx),
- };
- let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
- is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
- },
- _ => false,
- };
-
- let method_to_use = if is_map_to_option {
- // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
- "filter_map"
- } else {
- // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
- "flat_map"
- };
- let func_snippet = snippet(cx, map_arg.span, "..");
- let hint = format!(".{0}({1})", method_to_use, func_snippet);
- span_lint_and_sugg(
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, map_arg: &Expr<'_>, map_span: Span) {
+ if let Some((caller_ty_name, method_to_use)) = try_get_caller_ty_name_and_method_name(cx, expr, recv, map_arg) {
+ let mut applicability = Applicability::MachineApplicable;
+ let help_msgs = [
+ &format!("try replacing `map` with `{}`", method_to_use),
+ "and remove the `.flatten()`",
+ ];
+ let closure_snippet = snippet_with_applicability(cx, map_arg.span, "..", &mut applicability);
+ span_lint_and_sugg_for_edges(
cx,
MAP_FLATTEN,
- expr.span.with_lo(recv.span.hi()),
- "called `map(..).flatten()` on an `Iterator`",
- &format!("try using `{}` instead", method_to_use),
- hint,
- Applicability::MachineApplicable,
+ expr.span.with_lo(map_span.lo()),
+ &format!("called `map(..).flatten()` on `{}`", caller_ty_name),
+ &help_msgs,
+ format!("{}({})", method_to_use, closure_snippet),
+ applicability,
);
}
+}
- // lint if caller of `.map().flatten()` is an Option or Result
- let caller_type = match cx.typeck_results().expr_ty(recv).kind() {
- ty::Adt(adt, _) => {
- if cx.tcx.is_diagnostic_item(sym::Option, adt.did) {
- "Option"
- } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did) {
- "Result"
- } else {
- return;
+fn try_get_caller_ty_name_and_method_name(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ caller_expr: &Expr<'_>,
+ map_arg: &Expr<'_>,
+) -> Option<(&'static str, &'static str)> {
+ if is_trait_method(cx, expr, sym::Iterator) {
+ if is_map_to_option(cx, map_arg) {
+ // `(...).map(...)` has type `impl Iterator<Item=Option<...>>
+ Some(("Iterator", "filter_map"))
+ } else {
+ // `(...).map(...)` has type `impl Iterator<Item=impl Iterator<...>>
+ Some(("Iterator", "flat_map"))
+ }
+ } else {
+ if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) {
+ return Some(("Option", "and_then"));
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) {
+ return Some(("Result", "and_then"));
}
- },
- _ => {
- return;
- },
- };
+ }
+ None
+ }
+}
- let func_snippet = snippet(cx, map_arg.span, "..");
- let hint = format!(".and_then({})", func_snippet);
- let lint_info = format!("called `map(..).flatten()` on an `{}`", caller_type);
- span_lint_and_sugg(
- cx,
- MAP_FLATTEN,
- expr.span.with_lo(recv.span.hi()),
- &lint_info,
- "try using `and_then` instead",
- hint,
- Applicability::MachineApplicable,
- );
+fn is_map_to_option(cx: &LateContext<'_>, map_arg: &Expr<'_>) -> bool {
+ let map_closure_ty = cx.typeck_results().expr_ty(map_arg);
+ match map_closure_ty.kind() {
+ ty::Closure(_, _) | ty::FnDef(_, _) | ty::FnPtr(_) => {
+ let map_closure_sig = match map_closure_ty.kind() {
+ ty::Closure(_, substs) => substs.as_closure().sig(),
+ _ => map_closure_ty.fn_sig(cx.tcx),
+ };
+ let map_closure_return_ty = cx.tcx.erase_late_bound_regions(map_closure_sig.output());
+ is_type_diagnostic_item(cx, map_closure_return_ty, sym::Option)
+ },
+ _ => false,
+ }
}
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;
mod option_map_or_none;
mod option_map_unwrap_or;
mod or_fun_call;
+mod or_then_unwrap;
mod search_is_some;
mod single_char_add_str;
mod single_char_insert_string;
mod unnecessary_filter_map;
mod unnecessary_fold;
mod unnecessary_iter_cloned;
+mod unnecessary_join;
mod unnecessary_lazy_eval;
mod unnecessary_to_owned;
mod unwrap_or_else_default;
"using any `*or` method with a function call, which suggests `*or_else`"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `.or(…).unwrap()` calls to Options and Results.
+ ///
+ /// ### Why is this bad?
+ /// You should use `.unwrap_or(…)` instead for clarity.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # type Error = &'static str;
+ /// # let result: Result<&str, Error> = Err("error");
+ /// let value = result.or::<Error>(Ok(fallback)).unwrap();
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.or(Some(fallback)).unwrap();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let fallback = "fallback";
+ /// // Result
+ /// # let result: Result<&str, &str> = Err("error");
+ /// let value = result.unwrap_or(fallback);
+ ///
+ /// // Option
+ /// # let option: Option<&str> = None;
+ /// let value = option.unwrap_or(fallback);
+ /// ```
+ #[clippy::version = "1.61.0"]
+ pub OR_THEN_UNWRAP,
+ complexity,
+ "checks for `.or(…).unwrap()` calls to Options and Results."
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `.expect(&format!(...))`, `.expect(foo(..))`,
"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,
+ nursery,
+ "replace `.drain(..)` with `.into_iter()`"
+}
+
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
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.
///
"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`
"unnecessary calls to `to_owned`-like functions"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for use of `.collect::<Vec<String>>().join("")` on iterators.
+ ///
+ /// ### Why is this bad?
+ /// `.collect::<String>()` is more concise and usually more performant
+ ///
+ /// ### Example
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<Vec<String>>().join("");
+ /// println!("{}", output);
+ /// ```
+ /// The correct use would be:
+ /// ```rust
+ /// let vector = vec!["hello", "world"];
+ /// let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ /// println!("{}", output);
+ /// ```
+ /// ### Known problems
+ /// While `.collect::<String>()` is more performant in most cases, there are cases where
+ /// using `.collect::<String>()` over `.collect::<Vec<String>>().join("")`
+ /// will prevent loop unrolling and will result in a negative performance impact.
+ #[clippy::version = "1.61.0"]
+ pub UNNECESSARY_JOIN,
+ pedantic,
+ "using `.collect::<Vec<String>>().join(\"\")` on an iterator"
+}
+
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
OPTION_MAP_OR_NONE,
BIND_INSTEAD_OF_MAP,
OR_FUN_CALL,
+ OR_THEN_UNWRAP,
EXPECT_FUN_CALL,
CHARS_NEXT_CMP,
CHARS_LAST_CMP,
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,
MANUAL_SPLIT_ONCE,
NEEDLESS_SPLITN,
UNNECESSARY_TO_OWNED,
+ UNNECESSARY_JOIN,
]);
/// Extracts a method call name, args, and `Span` of the method name.
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),
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);
},
(name @ "flatten", args @ []) => match method_call(recv) {
- Some(("map", [recv, map_arg], _)) => map_flatten::check(cx, expr, recv, map_arg),
+ Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span),
Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args),
_ => {},
},
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
+ ("join", [join_arg]) => {
+ if let Some(("collect", _, span)) = method_call(recv) {
+ unnecessary_join::check(cx, expr, recv, join_arg, span);
+ }
+ },
("last", args @ []) | ("skip", args @ [_]) => {
if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) {
if let ("cloned", []) = (name2, args2) {
Some(("get_mut", [recv, get_arg], _)) => {
get_unwrap::check(cx, expr, recv, get_arg, true);
},
+ Some(("or", [recv, or_arg], or_span)) => {
+ or_then_unwrap::check(cx, expr, recv, or_arg, or_span);
+ },
_ => {},
}
unwrap_used::check(cx, expr, recv);
// 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, _) => {
(&[], 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,
}
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 {
--- /dev/null
+use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::ty::is_type_diagnostic_item;
+use clippy_utils::{diagnostics::span_lint_and_sugg, is_lang_ctor};
+use rustc_errors::Applicability;
+use rustc_hir::{lang_items::LangItem, Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_span::{sym, Span};
+
+use super::OR_THEN_UNWRAP;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ unwrap_expr: &Expr<'_>,
+ recv: &'tcx Expr<'tcx>,
+ or_arg: &'tcx Expr<'_>,
+ or_span: Span,
+) {
+ let ty = cx.typeck_results().expr_ty(recv); // get type of x (we later check if it's Option or Result)
+ let title;
+ let or_arg_content: Span;
+
+ if is_type_diagnostic_item(cx, ty, sym::Option) {
+ title = "found `.or(Some(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::OptionSome) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else if is_type_diagnostic_item(cx, ty, sym::Result) {
+ title = "found `.or(Ok(…)).unwrap()`";
+ if let Some(content) = get_content_if_ctor_matches(cx, or_arg, LangItem::ResultOk) {
+ or_arg_content = content;
+ } else {
+ return;
+ }
+ } else {
+ // Someone has implemented a struct with .or(...).unwrap() chaining,
+ // but it's not an Option or a Result, so bail
+ return;
+ }
+
+ let mut applicability = Applicability::MachineApplicable;
+ let suggestion = format!(
+ "unwrap_or({})",
+ snippet_with_applicability(cx, or_arg_content, "..", &mut applicability)
+ );
+
+ span_lint_and_sugg(
+ cx,
+ OR_THEN_UNWRAP,
+ unwrap_expr.span.with_lo(or_span.lo()),
+ title,
+ "try this",
+ suggestion,
+ applicability,
+ );
+}
+
+fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: LangItem) -> Option<Span> {
+ if let ExprKind::Call(some_expr, [arg]) = expr.kind
+ && let ExprKind::Path(qpath) = &some_expr.kind
+ && is_lang_ctor(cx, qpath, item)
+ {
+ Some(arg.span)
+ } else {
+ None
+ }
+}
return if_chain! {
if match_def_path(cx, did, &paths::ITERTOOLS_NEXT_TUPLE);
if let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind();
- if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did);
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did());
if let ty::Tuple(subs) = subs.type_at(0).kind();
if subs.len() == 2;
then {
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;
}
let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
let sugg = if !found_filtering {
- "map"
+ 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,
}
};
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),
);
}
}
--- /dev/null
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::is_type_diagnostic_item};
+use rustc_ast::ast::LitKind;
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind};
+use rustc_lint::LateContext;
+use rustc_middle::ty::{Ref, Slice};
+use rustc_span::{sym, Span};
+
+use super::UNNECESSARY_JOIN;
+
+pub(super) fn check<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr: &'tcx Expr<'tcx>,
+ join_self_arg: &'tcx Expr<'tcx>,
+ join_arg: &'tcx Expr<'tcx>,
+ span: Span,
+) {
+ let applicability = Applicability::MachineApplicable;
+ let collect_output_adjusted_type = cx.typeck_results().expr_ty_adjusted(join_self_arg);
+ if_chain! {
+ // the turbofish for collect is ::<Vec<String>>
+ if let Ref(_, ref_type, _) = collect_output_adjusted_type.kind();
+ if let Slice(slice) = ref_type.kind();
+ if is_type_diagnostic_item(cx, *slice, sym::String);
+ // the argument for join is ""
+ if let ExprKind::Lit(spanned) = &join_arg.kind;
+ if let LitKind::Str(symbol, _) = spanned.node;
+ if symbol.is_empty();
+ then {
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_JOIN,
+ span.with_hi(expr.span.hi()),
+ r#"called `.collect<Vec<String>>().join("")` on an iterator"#,
+ "try using",
+ "collect::<String>()".to_owned(),
+ applicability,
+ );
+ }
+ }
+}
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eager_or_lazy, usage};
Applicability::MaybeIncorrect
};
- span_lint_and_sugg(
- cx,
- UNNECESSARY_LAZY_EVALUATIONS,
- expr.span,
- msg,
- &format!("use `{}` instead", simplify_using),
- format!(
- "{0}.{1}({2})",
- snippet(cx, recv.span, ".."),
- simplify_using,
- snippet(cx, body_expr.span, ".."),
- ),
- applicability,
- );
+ // This is a duplicate of what's happening in clippy_lints::methods::method_call,
+ // which isn't ideal, We want to get the method call span,
+ // but prefer to avoid changing the signature of the function itself.
+ if let hir::ExprKind::MethodCall(_, _, span) = expr.kind {
+ span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| {
+ diag.span_suggestion(
+ span,
+ &format!("use `{}(..)` instead", simplify_using),
+ format!("{}({})", simplify_using, snippet(cx, body_expr.span, "..")),
+ applicability,
+ );
+ });
+ }
}
}
}
use super::unnecessary_iter_cloned::{self, is_into_iter};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_opt;
-use clippy_utils::ty::{get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs};
+use clippy_utils::ty::{
+ contains_ty, get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs,
+};
use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item};
use rustc_errors::Applicability;
use rustc_hir::{def_id::DefId, BorrowKind, Expr, ExprKind};
parent.span,
&format!("unnecessary use of `{}`", method_name),
"use",
- format!("{:&>width$}{}", "", receiver_snippet, width = n_target_refs - n_receiver_refs),
+ format!(
+ "{:&>width$}{}",
+ "",
+ receiver_snippet,
+ width = n_target_refs - n_receiver_refs
+ ),
Applicability::MachineApplicable,
);
return true;
if let Some(item_ty) = get_iterator_item_ty(cx, parent_ty);
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
- if unnecessary_iter_cloned::check_for_loop_iter(
- cx,
- parent,
- method_name,
- receiver,
- true,
- ) {
+ if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) {
return true;
}
- let cloned_or_copied = if is_copy(cx, item_ty) {
- "copied"
- } else {
- "cloned"
- };
+ let cloned_or_copied = if is_copy(cx, item_ty) { "copied" } else { "cloned" };
// The next suggestion may be incorrect because the removal of the `to_owned`-like
// function could cause the iterator to hold a reference to a resource that is used
// mutably. See https://github.com/rust-lang/rust-clippy/issues/8148.
if if trait_predicate.def_id() == deref_trait_id {
if let [projection_predicate] = projection_predicates[..] {
let normalized_ty =
- cx.tcx.subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
+ cx.tcx
+ .subst_and_normalize_erasing_regions(call_substs, cx.param_env, projection_predicate.term);
implements_trait(cx, receiver_ty, deref_trait_id, &[])
- && get_associated_type(cx, receiver_ty, deref_trait_id,
- "Target").map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
+ && get_associated_type(cx, receiver_ty, deref_trait_id, "Target")
+ .map_or(false, |ty| ty::Term::Ty(ty) == normalized_ty)
} else {
false
}
let composed_substs = compose_substs(
cx,
&trait_predicate.trait_ref.substs.iter().skip(1).collect::<Vec<_>>()[..],
- call_substs
+ call_substs,
);
implements_trait(cx, receiver_ty, as_ref_trait_id, &composed_substs)
} else {
// `Target = T`.
if n_refs > 0 || is_copy(cx, receiver_ty) || trait_predicate.def_id() != deref_trait_id;
let n_refs = max(n_refs, if is_copy(cx, receiver_ty) { 0 } else { 1 });
+ // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
+ // `T` must not be instantiated with a reference
+ // (https://github.com/rust-lang/rust-clippy/issues/8507).
+ if (n_refs == 0 && !receiver_ty.is_ref())
+ || trait_predicate.def_id() != as_ref_trait_id
+ || !contains_ty(fn_sig.output(), input);
if let Some(receiver_snippet) = snippet_opt(cx, receiver.span);
then {
span_lint_and_sugg(
if let Some(arg) = substs.iter().next();
if let GenericArgKind::Type(arg_ty) = arg.unpack();
if arg_ty == input;
- then {
- true
- } else {
- false
- }
+ then { true } else { false }
}
};
match predicate.kind().skip_binder() {
use clippy_utils::ty::has_drop;
use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, meets_msrv, msrvs, trait_ref_of_method};
use rustc_hir as hir;
+use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Constness, FnDecl, GenericParamKind, HirId};
use rustc_lint::{LateContext, LateLintPass};
FnKind::Closure => return,
}
+ // Const fns are not allowed as methods in a trait.
+ {
+ let parent = cx.tcx.hir().get_parent_item(hir_id);
+ if parent != CRATE_DEF_ID {
+ if let hir::Node::Item(item) = cx.tcx.hir().get_by_def_id(parent) {
+ if let hir::ItemKind::Trait(..) = &item.kind {
+ return;
+ }
+ }
+ }
+ }
+
let mir = cx.tcx.optimized_mir(def_id);
if let Err((span, err)) = is_min_const_fn(cx.tcx, mir, self.msrv.as_ref()) {
if let Adt(def, substs) = ty.kind() {
let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
.iter()
- .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
if is_keyed_type && is_interior_mutable_type(cx, substs.type_at(0), span) {
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
}
sym::Arc,
]
.iter()
- .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did));
- let is_box = Some(def.did) == cx.tcx.lang_items().owned_box();
+ .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
+ let is_box = Some(def.did()) == cx.tcx.lang_items().owned_box();
if is_std_collection || is_box {
// The type is mutable if any of its type parameters are
substs.types().any(|ty| is_interior_mutable_type(cx, ty, span))
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BindingAnnotation, Body, FnDecl, GenericArg, HirId, Impl, ItemKind, Node, PatKind, QPath, TyKind};
use rustc_hir::{HirIdMap, HirIdSet};
}
// Dereference suggestion
- let sugg = |diag: &mut DiagnosticBuilder<'_>| {
+ 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).is_ok() {
+ 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() {
diag.span_help(span, "consider marking this type as `Copy`");
}
}
let ty = cx.typeck_results().expr_ty(expr);
if let ty::Adt(def, _) = ty.kind() {
if fields.len() == def.non_enum_variant().fields.len()
- && !def.variants[0_usize.into()].is_field_list_non_exhaustive()
+ && !def.variant(0_usize.into()).is_field_list_non_exhaustive()
{
span_lint(
cx,
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::return_ty;
use clippy_utils::source::snippet;
-use clippy_utils::sugg::DiagnosticBuilderExt;
+use clippy_utils::sugg::DiagnosticExt;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
let mut impls = HirIdSet::default();
cx.tcx.for_each_impl(default_trait_id, |d| {
if let Some(ty_def) = cx.tcx.type_of(d).ty_adt_def() {
- if let Some(local_def_id) = ty_def.did.as_local() {
+ if let Some(local_def_id) = ty_def.did().as_local() {
impls.insert(cx.tcx.hir().local_def_id_to_hir_id(local_def_id));
}
}
if_chain! {
if let Some(ref impling_types) = self.impling_types;
if let Some(self_def) = cx.tcx.type_of(self_def_id).ty_adt_def();
- if let Some(self_local_did) = self_def.did.as_local();
+ if let Some(self_local_did) = self_def.did().as_local();
let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
if impling_types.contains(&self_id);
then {
match val.ty().kind() {
// the fact that we have to dig into every structs to search enums
// leads us to the point checking `UnsafeCell` directly is the only option.
- ty::Adt(ty_def, ..) if Some(ty_def.did) == cx.tcx.lang_items().unsafe_cell_type() => true,
+ ty::Adt(ty_def, ..) if Some(ty_def.did()) == cx.tcx.lang_items().unsafe_cell_type() => true,
ty::Array(..) | ty::Adt(..) | ty::Tuple(..) => {
let val = cx.tcx.destructure_const(cx.param_env.and(val));
val.fields.iter().any(|field| inner(cx, *field))
let mut non_send_fields = Vec::new();
let hir_map = cx.tcx.hir();
- for variant in &adt_def.variants {
+ for variant in adt_def.variants() {
for field in &variant.fields {
if_chain! {
if let Some(field_hir_id) = field
return true;
},
ty::Adt(adt_def, _) => {
- if match_def_path(cx, adt_def.did, &paths::PTR_NON_NULL) {
+ if match_def_path(cx, adt_def.did(), &paths::PTR_NON_NULL) {
return true;
}
},
--- /dev/null
+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 example 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;
+ }
+ }
+}
// Check that the name as typed matches the actual name of the type.
// e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
if let [.., name] = path.segments;
- if cx.tcx.item_name(adt.did) == name.ident.name;
+ if cx.tcx.item_name(adt.did()) == name.ident.name;
if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
then {
- let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
+ let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did()) {
Some(sym::Vec) => (
[("clone", ".to_owned()")].as_slice(),
DerefTy::Slice(
DerefTy::Path,
None,
),
- Some(sym::Cow) => {
+ Some(sym::Cow) if mutability == Mutability::Not => {
let ty_name = name.args
.and_then(|args| {
args.args.iter().find_map(|a| match a {
return Some(PtrArg {
idx: i,
span: hir_ty.span,
- ty_did: adt.did,
+ ty_did: adt.did(),
ty_name: name.ident.name,
method_renames,
ref_prefix: RefPrefix {
.map(|sig| sig.input(i).skip_binder().peel_refs())
.map_or(true, |ty| match *ty.kind() {
ty::Param(_) => true,
- ty::Adt(def, _) => def.did == args.ty_did,
+ ty::Adt(def, _) => def.did() == args.ty_did,
_ => false,
})
{
// If the types match check for methods which exist on both types. e.g. `Vec::len` and
// `slice::len`
ty::Adt(def, _)
- if def.did == args.ty_did
+ if def.did() == args.ty_did
&& (i != 0
|| self.cx.tcx.trait_of_item(id).is_some()
|| !args.deref_assoc_items.map_or(false, |(id, items)| {
}
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 {
}
}
- 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,
}
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};
/// 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>>,
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);
}
}
/// 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>,
}
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
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
+ }
+}
if_chain! {
if let Some(self_def) = self_ty.ty_adt_def();
- if let Some(self_local_did) = self_def.did.as_local();
+ if let Some(self_local_did) = self_def.did().as_local();
let self_id = cx.tcx.hir().local_def_id_to_hir_id(self_local_did);
if let Some(Node::Item(x)) = cx.tcx.hir().find(self_id);
let type_name = x.ident.name.as_str().to_lowercase();
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
-use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind, VisibilityKind};
+use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::{declare_lint_pass, declare_tool_lint};
);
}
- for single_use in &single_use_usages {
- if !imports_reused_with_self.contains(&single_use.0) {
- let can_suggest = single_use.2;
+ for (name, span, can_suggest) in single_use_usages {
+ if !imports_reused_with_self.contains(&name) {
if can_suggest {
span_lint_and_sugg(
cx,
SINGLE_COMPONENT_PATH_IMPORTS,
- single_use.1,
+ span,
"this import is redundant",
"remove it entirely",
String::new(),
span_lint_and_help(
cx,
SINGLE_COMPONENT_PATH_IMPORTS,
- single_use.1,
+ span,
"this import is redundant",
None,
"remove this import",
ItemKind::Use(use_tree) => {
let segments = &use_tree.prefix.segments;
- let should_report =
- |name: &Symbol| !macros.contains(name) || matches!(item.vis.kind, VisibilityKind::Inherited);
-
// keep track of `use some_module;` usages
if segments.len() == 1 {
if let UseTreeKind::Simple(None, _, _) = use_tree.kind {
let name = segments[0].ident.name;
- if should_report(&name) {
+ if !macros.contains(&name) {
single_use_usages.push((name, item.span, true));
}
}
if segments.len() == 1 {
if let UseTreeKind::Simple(None, _, _) = tree.0.kind {
let name = segments[0].ident.name;
- if should_report(&name) {
+ if !macros.contains(&name) {
single_use_usages.push((name, tree.0.span, false));
}
}
// used instead, in these cases.
*applicability = Applicability::MaybeIncorrect;
- // We arbitraily choose one side to suggest changing,
+ // We arbitrarily choose one side to suggest changing,
// since we don't have a better guess. If the user
// ends up duplicating a clause, the `logic_bug` lint
// should catch it.
}
fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
- append_opt_vecs(chained_binops(kind), if_statment_binops(kind))
+ append_opt_vecs(chained_binops(kind), if_statement_binops(kind))
}
-fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
+fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
match kind {
ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
- ExprKind::Paren(ref e) => if_statment_binops(&e.kind),
+ ExprKind::Paren(ref e) => if_statement_binops(&e.kind),
ExprKind::Block(ref block, _) => {
let mut output = None;
for stmt in &block.stmts {
match stmt.kind {
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
- output = append_opt_vecs(output, if_statment_binops(&e.kind));
+ output = append_opt_vecs(output, if_statement_binops(&e.kind));
},
_ => {},
}
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)
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)
/// Displays a warning when a struct with a trailing zero-sized array is declared without a `repr` attribute.
///
/// ### Why is this bad?
- /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjuction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
+ /// Zero-sized arrays aren't very useful in Rust itself, so such a struct is likely being created to pass to C code or in some other situation where control over memory layout matters (for example, in conjunction with manual allocation to make it easy to compute the offset of the array). Either way, `#[repr(C)]` (or another `repr` attribute) is needed.
///
/// ### Example
/// ```rust
///
/// ### Why is this bad?
/// Duplicate bounds makes the code
- /// less readable than specifing them only once.
+ /// less readable than specifying them only once.
///
/// ### Example
/// ```rust
/// ### 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);
/// ```
#[clippy::version = "1.60.0"]
pub TRANSMUTE_UNDEFINED_REPR,
- correctness,
+ nursery,
"transmute to or from a type with an undefined representation"
}
// And see https://github.com/rust-lang/rust/issues/51911 for dereferencing raw pointers.
let const_context = in_constant(cx, e.hir_id);
- let from_ty = cx.typeck_results().expr_ty(arg);
+ let from_ty = cx.typeck_results().expr_ty_adjusted(arg);
+ // Adjustments for `to_ty` happen after the call to `transmute`, so don't use them.
let to_ty = cx.typeck_results().expr_ty(e);
// If useless_transmute is triggered, the other lints can be skipped.
use clippy_utils::ty::is_c_void;
use rustc_hir::Expr;
use rustc_lint::LateContext;
-use rustc_middle::ty::subst::Subst;
-use rustc_middle::ty::{self, Ty, TypeAndMut};
+use rustc_middle::ty::subst::{Subst, SubstsRef};
+use rustc_middle::ty::{self, IntTy, Ty, TypeAndMut, UintTy};
use rustc_span::Span;
#[allow(clippy::too_many_lines)]
unsized_ty,
to_ty: to_sub_ty,
} => match reduce_ty(cx, to_sub_ty) {
- ReducedTy::IntArray | ReducedTy::TypeErasure => break,
+ ReducedTy::TypeErasure => break,
+ ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break,
ReducedTy::Ref(to_sub_ty) => {
from_ty = unsized_ty;
to_ty = to_sub_ty;
unsized_ty,
from_ty: from_sub_ty,
} => match reduce_ty(cx, from_sub_ty) {
- ReducedTy::IntArray | ReducedTy::TypeErasure => break,
+ ReducedTy::TypeErasure => break,
+ ReducedTy::UnorderedFields(ty) if is_size_pair(ty) => break,
ReducedTy::Ref(from_sub_ty) => {
from_ty = from_sub_ty;
to_ty = unsized_ty;
from_ty: from_sub_ty,
to_ty: to_sub_ty,
} => match (reduce_ty(cx, from_sub_ty), reduce_ty(cx, to_sub_ty)) {
- (ReducedTy::IntArray | ReducedTy::TypeErasure, _)
- | (_, ReducedTy::IntArray | ReducedTy::TypeErasure) => return false,
+ (ReducedTy::TypeErasure, _) | (_, ReducedTy::TypeErasure) => return false,
(ReducedTy::UnorderedFields(from_ty), ReducedTy::UnorderedFields(to_ty)) if from_ty != to_ty => {
+ let same_adt_did = if let (ty::Adt(from_def, from_subs), ty::Adt(to_def, to_subs))
+ = (from_ty.kind(), to_ty.kind())
+ && from_def == to_def
+ {
+ if same_except_params(from_subs, to_subs) {
+ return false;
+ }
+ Some(from_def.did())
+ } else {
+ None
+ };
span_lint_and_then(
cx,
TRANSMUTE_UNDEFINED_REPR,
from_ty_orig, to_ty_orig
),
|diag| {
- if_chain! {
- if let (Some(from_def), Some(to_def)) = (from_ty.ty_adt_def(), to_ty.ty_adt_def());
- if from_def == to_def;
- then {
- diag.note(&format!(
- "two instances of the same generic type (`{}`) may have different layouts",
- cx.tcx.item_name(from_def.did)
- ));
- } else {
- if from_ty_orig.peel_refs() != from_ty {
- diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
- }
- if to_ty_orig.peel_refs() != to_ty {
- diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
- }
+ if let Some(same_adt_did) = same_adt_did {
+ diag.note(&format!(
+ "two instances of the same generic type (`{}`) may have different layouts",
+ cx.tcx.item_name(same_adt_did)
+ ));
+ } else {
+ if from_ty_orig.peel_refs() != from_ty {
+ diag.note(&format!("the contained type `{}` has an undefined layout", from_ty));
+ }
+ if to_ty_orig.peel_refs() != to_ty {
+ diag.note(&format!("the contained type `{}` has an undefined layout", to_ty));
}
}
},
continue;
},
(
- ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
- ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_),
+ ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
+ ReducedTy::OrderedFields(_) | ReducedTy::Ref(_) | ReducedTy::Other(_) | ReducedTy::Param,
)
- | (ReducedTy::UnorderedFields(_), ReducedTy::UnorderedFields(_)) => break,
+ | (
+ ReducedTy::UnorderedFields(_) | ReducedTy::Param,
+ ReducedTy::UnorderedFields(_) | ReducedTy::Param,
+ ) => break,
},
}
}
UnorderedFields(Ty<'tcx>),
/// The type is a reference to the contained type.
Ref(Ty<'tcx>),
- /// The type is an array of a primitive integer type. These can be used as storage for a value
- /// of another type.
- IntArray,
+ /// The type is a generic parameter.
+ Param,
/// Any other type.
Other(Ty<'tcx>),
}
loop {
ty = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty).unwrap_or(ty);
return match *ty.kind() {
- ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::IntArray,
+ ty::Array(sub_ty, _) if matches!(sub_ty.kind(), ty::Int(_) | ty::Uint(_)) => ReducedTy::TypeErasure,
ty::Array(sub_ty, _) | ty::Slice(sub_ty) => {
ty = sub_ty;
continue;
},
ty::Tuple(args) if args.is_empty() => ReducedTy::TypeErasure,
ty::Tuple(args) => {
- let Some(sized_ty) = args.iter().find(|&ty| !is_zero_sized_ty(cx, ty)) else {
+ let mut iter = args.iter();
+ let Some(sized_ty) = iter.find(|&ty| !is_zero_sized_ty(cx, ty)) else {
return ReducedTy::OrderedFields(ty);
};
- if args.iter().all(|ty| is_zero_sized_ty(cx, ty)) {
+ if iter.all(|ty| is_zero_sized_ty(cx, ty)) {
ty = sized_ty;
continue;
}
ty = sized_ty;
continue;
}
- if def.repr.inhibit_struct_field_reordering_opt() {
+ if def.repr().inhibit_struct_field_reordering_opt() {
ReducedTy::OrderedFields(ty)
} else {
ReducedTy::UnorderedFields(ty)
}
},
- ty::Adt(def, _) if def.is_enum() && (def.variants.is_empty() || is_c_void(cx, ty)) => {
+ ty::Adt(def, _) if def.is_enum() && (def.variants().is_empty() || is_c_void(cx, ty)) => {
ReducedTy::TypeErasure
},
+ // TODO: Check if the conversion to or from at least one of a union's fields is valid.
+ ty::Adt(def, _) if def.is_union() => ReducedTy::TypeErasure,
ty::Foreign(_) => ReducedTy::TypeErasure,
ty::Ref(_, ty, _) => ReducedTy::Ref(ty),
ty::RawPtr(ty) => ReducedTy::Ref(ty.ty),
+ ty::Param(_) => ReducedTy::Param,
_ => ReducedTy::Other(ty),
};
}
if let Ok(ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, ty);
if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty));
then {
- layout.layout.size.bytes() == 0
+ layout.layout.size().bytes() == 0
} else {
false
}
}
}
+
+fn is_size_pair(ty: Ty<'_>) -> bool {
+ if let ty::Tuple(tys) = *ty.kind()
+ && let [ty1, ty2] = &**tys
+ {
+ matches!(ty1.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+ && matches!(ty2.kind(), ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize))
+ } else {
+ false
+ }
+}
+
+fn same_except_params(subs1: SubstsRef<'_>, subs2: SubstsRef<'_>) -> bool {
+ // TODO: check const parameters as well. Currently this will consider `Array<5>` the same as
+ // `Array<6>`
+ for (ty1, ty2) in subs1.types().zip(subs2.types()).filter(|(ty1, ty2)| ty1 != ty2) {
+ match (ty1.kind(), ty2.kind()) {
+ (ty::Param(_), _) | (_, ty::Param(_)) => (),
+ (ty::Adt(adt1, subs1), ty::Adt(adt2, subs2)) if adt1 == adt2 && same_except_params(subs1, subs2) => (),
+ _ => return false,
+ }
+ }
+ true
+}
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>) -> bool {
match (&from_ty.kind(), &to_ty.kind()) {
(ty::Adt(from_adt, from_substs), ty::Adt(to_adt, to_substs)) => {
- if from_adt.did != to_adt.did {
+ if from_adt.did() != to_adt.did() {
return false;
}
if !matches!(
- cx.tcx.get_diagnostic_name(to_adt.did),
+ cx.tcx.get_diagnostic_name(to_adt.did()),
Some(
sym::BTreeMap
| sym::BTreeSet
/// ```
#[clippy::version = "1.38.0"]
pub TRY_ERR,
- style,
+ restriction,
"return errors explicitly rather than hiding them behind a `?`"
}
fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
- if match_def_path(cx, def.did, &paths::POLL);
+ if match_def_path(cx, def.did(), &paths::POLL);
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
- if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did);
+ if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did());
then {
Some(ready_subst.type_at(1))
} else {
fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
- if match_def_path(cx, def.did, &paths::POLL);
+ if match_def_path(cx, def.did(), &paths::POLL);
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
- if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did);
+ if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did());
let some_ty = ready_subst.type_at(0);
if let ty::Adt(some_def, some_subst) = some_ty.kind();
- if cx.tcx.is_diagnostic_item(sym::Result, some_def.did);
+ if cx.tcx.is_diagnostic_item(sym::Result, some_def.did());
then {
Some(some_subst.type_at(1))
} else {
/// 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
(
(
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,
}
}
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;
// Get the wrapper and inner types, if can't, abort.
let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
- if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did) {
+ if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) {
("Option", OptionSome, subst.type_at(0))
- } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did) {
+ } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) {
("Result", ResultOk, subst.type_at(0))
} else {
return;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
// check for `expect`
if let Some(arglists) = method_chain_args(expr, &["expect"]) {
- let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
- if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
- || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
{
self.result.push(expr.span);
}
// check for `unwrap`
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
- let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
- if is_type_diagnostic_item(self.lcx, reciever_ty, sym::Option)
- || is_type_diagnostic_item(self.lcx, reciever_ty, sym::Result)
+ let receiver_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs();
+ if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option)
+ || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result)
{
self.result.push(expr.span);
}
check_ident(cx, &it.ident, self.upper_case_acronyms_aggressive);
},
ItemKind::Enum(ref enumdef, _) => {
- // check enum variants seperately because again we only want to lint on private enums and
+ // check enum variants separately because again we only want to lint on private enums and
// the fn check_variant does not know about the vis of the enum of its variants
enumdef
.variants
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;
}
}
+ 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);
}
let item_ty = cx.tcx.type_of(did);
println!("function of type {:#?}", item_ty);
},
- hir::ItemKind::Macro(ref macro_def) => {
+ hir::ItemKind::Macro(ref macro_def, _) => {
if macro_def.macro_rules {
println!("macro introduced by `macro_rules!`");
} else {
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;
"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 {
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,
+ );
+ }
+ }
+ }
+}
};
}
-const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
+const LINT_EMISSION_FUNCTIONS: [&[&str]; 8] = [
&["clippy_utils", "diagnostics", "span_lint"],
&["clippy_utils", "diagnostics", "span_lint_and_help"],
&["clippy_utils", "diagnostics", "span_lint_and_note"],
&["clippy_utils", "diagnostics", "span_lint_and_sugg"],
&["clippy_utils", "diagnostics", "span_lint_and_then"],
&["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
+ &["clippy_utils", "diagnostics", "span_lint_and_sugg_for_edges"],
];
const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
("span_suggestion", false),
/// ```
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
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;
}
lints
- .drain(..)
+ .into_iter()
.map(|lint_name| (lint_name, applicability, multi_part))
.collect()
}
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;
match parser
.parse_expr()
.map(rustc_ast::ptr::P::into_inner)
- .map_err(|mut e| e.cancel())
+ .map_err(DiagnosticBuilder::cancel)
{
// write!(e, ...)
Ok(p) if parser.eat(&token::Comma) => Some(p),
}
let comma_span = parser.prev_token.span;
- let token_expr = if let Ok(expr) = parser.parse_expr().map_err(|mut err| err.cancel()) {
+ let token_expr = if let Ok(expr) = parser.parse_expr().map_err(DiagnosticBuilder::cancel) {
expr
} else {
return (Some(fmtstr), None);
};
let replacement: String = match lit.token.kind {
- LitKind::Integer | LitKind::Float | LitKind::Err => continue,
LitKind::StrRaw(_) | LitKind::ByteStrRaw(_) if matches!(fmtstr.style, StrStyle::Raw(_)) => {
lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
},
LitKind::Str | LitKind::ByteStr if matches!(fmtstr.style, StrStyle::Cooked) => {
lit.token.symbol.as_str().replace('{', "{{").replace('}', "}}")
},
- LitKind::StrRaw(_) | LitKind::Str | LitKind::ByteStrRaw(_) | LitKind::ByteStr => continue,
+ LitKind::StrRaw(_)
+ | LitKind::Str
+ | LitKind::ByteStrRaw(_)
+ | LitKind::ByteStr
+ | LitKind::Integer
+ | LitKind::Float
+ | LitKind::Err => continue,
LitKind::Byte | LitKind::Char => match lit.token.symbol.as_str() {
"\"" if matches!(fmtstr.style, StrStyle::Cooked) => "\\\"",
"\"" if matches!(fmtstr.style, StrStyle::Raw(0)) => continue,
generics: lg,
bounds: lb,
ty: lt,
+ ..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
+ ..
}),
) => {
eq_defaultness(*ld, *rd)
generics: lg,
bounds: lb,
ty: lt,
+ ..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
+ ..
}),
) => {
eq_defaultness(*ld, *rd)
generics: lg,
bounds: lb,
ty: lt,
+ ..
}),
TyAlias(box ast::TyAlias {
defaultness: rd,
generics: rg,
bounds: rb,
ty: rt,
+ ..
}),
) => {
eq_defaultness(*ld, *rd)
ty::ConstKind::Value(ConstValue::Slice { data, start, end }) => match result.ty().kind() {
ty::Ref(_, tam, _) => match tam.kind() {
ty::Str => String::from_utf8(
- data.inspect_with_uninit_and_ptr_outside_interpreter(start..end)
+ data.inner()
+ .inspect_with_uninit_and_ptr_outside_interpreter(start..end)
.to_owned(),
)
.ok()
ty::Array(sub_type, len) => match sub_type.kind() {
ty::Float(FloatTy::F32) => match miri_to_const(*len) {
Some(Constant::Int(len)) => alloc
+ .inner()
.inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize))
.to_owned()
.chunks(4)
},
ty::Float(FloatTy::F64) => match miri_to_const(*len) {
Some(Constant::Int(len)) => alloc
+ .inner()
.inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize))
.to_owned()
.chunks(8)
//! Thank you!
//! ~The `INTERNAL_METADATA_COLLECTOR` lint
-use rustc_errors::{Applicability, DiagnosticBuilder};
+use rustc_errors::{emitter::MAX_SUGGESTION_HIGHLIGHT_LINES, Applicability, Diagnostic};
use rustc_hir::HirId;
use rustc_lint::{LateContext, Lint, LintContext};
use rustc_span::source_map::{MultiSpan, Span};
use std::env;
-fn docs_link(diag: &mut DiagnosticBuilder<'_>, lint: &'static Lint) {
+fn docs_link(diag: &mut Diagnostic, lint: &'static Lint) {
if env::var("CLIPPY_DISABLE_DOCS_LINKS").is_err() {
if let Some(lint) = lint.name_lower().strip_prefix("clippy::") {
diag.help(&format!(
where
C: LintContext,
S: Into<MultiSpan>,
- F: FnOnce(&mut DiagnosticBuilder<'_>),
+ F: FnOnce(&mut Diagnostic),
{
cx.struct_span_lint(lint, sp, |diag| {
let mut diag = diag.build(msg);
hir_id: HirId,
sp: impl Into<MultiSpan>,
msg: &str,
- f: impl FnOnce(&mut DiagnosticBuilder<'_>),
+ f: impl FnOnce(&mut Diagnostic),
) {
cx.tcx.struct_span_lint_hir(lint, hir_id, sp, |diag| {
let mut diag = diag.build(msg);
});
}
+/// Like [`span_lint_and_sugg`] with a focus on the edges. The output will either
+/// emit single span or multispan suggestion depending on the number of its lines.
+///
+/// If the given suggestion string has more lines than the maximum display length defined by
+/// [`MAX_SUGGESTION_HIGHLIGHT_LINES`][`rustc_errors::emitter::MAX_SUGGESTION_HIGHLIGHT_LINES`],
+/// this function will split the suggestion and span to showcase the change for the top and
+/// bottom edge of the code. For normal suggestions, in one display window, the help message
+/// will be combined with a colon.
+///
+/// Multipart suggestions like the one being created here currently cannot be
+/// applied by rustfix (See [rustfix#141](https://github.com/rust-lang/rustfix/issues/141)).
+/// Testing rustfix with this lint emission function might require a file with
+/// suggestions that can be fixed and those that can't. See
+/// [clippy#8520](https://github.com/rust-lang/rust-clippy/pull/8520/files) for
+/// an example and of this.
+///
+/// # Example for a long suggestion
+///
+/// ```text
+/// error: called `map(..).flatten()` on `Option`
+/// --> $DIR/map_flatten.rs:8:10
+/// |
+/// LL | .map(|x| {
+/// | __________^
+/// LL | | if x <= 5 {
+/// LL | | Some(x)
+/// LL | | } else {
+/// ... |
+/// LL | | })
+/// LL | | .flatten();
+/// | |__________________^
+/// |
+/// = note: `-D clippy::map-flatten` implied by `-D warnings`
+/// help: try replacing `map` with `and_then`
+/// |
+/// LL ~ .and_then(|x| {
+/// LL + if x <= 5 {
+/// LL + Some(x)
+/// |
+/// help: and remove the `.flatten()`
+/// |
+/// LL + None
+/// LL + }
+/// LL ~ });
+/// |
+/// ```
+pub fn span_lint_and_sugg_for_edges(
+ cx: &LateContext<'_>,
+ lint: &'static Lint,
+ sp: Span,
+ msg: &str,
+ helps: &[&str; 2],
+ sugg: String,
+ applicability: Applicability,
+) {
+ span_lint_and_then(cx, lint, sp, msg, |diag| {
+ let sugg_lines_count = sugg.lines().count();
+ if sugg_lines_count > MAX_SUGGESTION_HIGHLIGHT_LINES {
+ let sm = cx.sess().source_map();
+ if let (Ok(line_upper), Ok(line_bottom)) = (sm.lookup_line(sp.lo()), sm.lookup_line(sp.hi())) {
+ let split_idx = MAX_SUGGESTION_HIGHLIGHT_LINES / 2;
+ let span_upper = sm.span_until_char(sp.with_hi(line_upper.sf.lines[line_upper.line + split_idx]), '\n');
+ let span_bottom = sp.with_lo(line_bottom.sf.lines[line_bottom.line - split_idx]);
+
+ let sugg_lines_vec = sugg.lines().collect::<Vec<&str>>();
+ let sugg_upper = sugg_lines_vec[..split_idx].join("\n");
+ let sugg_bottom = sugg_lines_vec[sugg_lines_count - split_idx..].join("\n");
+
+ diag.span_suggestion(span_upper, helps[0], sugg_upper, applicability);
+ diag.span_suggestion(span_bottom, helps[1], sugg_bottom, applicability);
+
+ return;
+ }
+ }
+ diag.span_suggestion_with_style(
+ sp,
+ &helps.join(", "),
+ sugg,
+ applicability,
+ rustc_errors::SuggestionStyle::ShowAlways,
+ );
+ });
+}
+
/// Create a suggestion made from several `span → replacement`.
///
/// Note: in the JSON format (used by `compiletest_rs`), the help message will
/// appear once per
/// replacement. In human-readable format though, it only appears once before
/// the whole suggestion.
-pub fn multispan_sugg<I>(diag: &mut DiagnosticBuilder<'_>, help_msg: &str, sugg: I)
+pub fn multispan_sugg<I>(diag: &mut Diagnostic, help_msg: &str, sugg: I)
where
I: IntoIterator<Item = (Span, String)>,
{
/// multiple spans. This is tracked in issue [rustfix#141](https://github.com/rust-lang/rustfix/issues/141).
/// Suggestions with multiple spans will be silently ignored.
pub fn multispan_sugg_with_applicability<I>(
- diag: &mut DiagnosticBuilder<'_>,
+ diag: &mut Diagnostic,
help_msg: &str,
applicability: Applicability,
sugg: I,
// than marker traits.
// Due to the limited operations on these types functions should be fairly cheap.
if def
- .variants
+ .variants()
.iter()
.flat_map(|v| v.fields.iter())
.any(|x| matches!(cx.tcx.type_of(x.did).peel_refs().kind(), ty::Param(_)))
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)
}
}
}
- 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,
pub fn is_diag_item_method(cx: &LateContext<'_>, def_id: DefId, diag_item: Symbol) -> bool {
if let Some(impl_did) = cx.tcx.impl_of_method(def_id) {
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
- return cx.tcx.is_diagnostic_item(diag_item, adt.did);
+ return cx.tcx.is_diagnostic_item(diag_item, adt.did());
}
}
false
fn find_crate(tcx: TyCtxt<'_>, name: &str) -> Option<DefId> {
tcx.crates(())
.iter()
- .find(|&&num| tcx.crate_name(num).as_str() == name)
+ .copied()
+ .find(|&num| tcx.crate_name(num).as_str() == name)
.map(CrateNum::as_def_id)
}
if let Some(adt) = cx.tcx.type_of(impl_did).ty_adt_def() {
return std_types_symbols
.iter()
- .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did));
+ .any(|&symbol| cx.tcx.is_diagnostic_item(symbol, adt.did()));
}
}
}
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 }
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"];
#[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"];
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"];
| ty::PredicateKind::Projection(_)
| ty::PredicateKind::ConstEvaluatable(..)
| ty::PredicateKind::ConstEquate(..)
+ | ty::PredicateKind::Trait(..)
| ty::PredicateKind::TypeWellFormedFromEnv(..) => continue,
ty::PredicateKind::ObjectSafe(_) => panic!("object safe predicate on function: {:#?}", predicate),
ty::PredicateKind::ClosureKind(..) => panic!("closure kind predicate on function: {:#?}", predicate),
ty::PredicateKind::Subtype(_) => panic!("subtype predicate on function: {:#?}", predicate),
ty::PredicateKind::Coerce(_) => panic!("coerce predicate on function: {:#?}", predicate),
- ty::PredicateKind::Trait(pred) => {
- if Some(pred.def_id()) == tcx.lang_items().sized_trait() {
- continue;
- }
- match pred.self_ty().kind() {
- ty::Param(ref p) => {
- let generics = tcx.generics_of(current);
- let def = generics.type_param(p, tcx);
- let span = tcx.def_span(def.def_id);
- return Err((
- span,
- "trait bounds other than `Sized` \
- on const fn parameters are unstable"
- .into(),
- ));
- },
- // other kinds of bounds are either tautologies
- // or cause errors in other passes
- _ => continue,
- }
- },
}
}
match predicates.parent {
})
}
-/// Convenience extension trait for `DiagnosticBuilder`.
-pub trait DiagnosticBuilderExt<T: LintContext> {
+/// Convenience extension trait for `Diagnostic`.
+pub trait DiagnosticExt<T: LintContext> {
/// Suggests to add an attribute to an item.
///
/// Correctly handles indentation of the attribute and item.
fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
}
-impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
+impl<T: LintContext> DiagnosticExt<T> for rustc_errors::Diagnostic {
fn suggest_item_with_attr<D: Display + ?Sized>(
&mut self,
cx: &T,
closure_arg_is_type_annotated_double_ref,
next_pos: closure.span.lo(),
suggestion_start: String::new(),
- applicability: Applicability::MaybeIncorrect,
+ applicability: Applicability::MachineApplicable,
};
let fn_def_id = cx.tcx.hir().local_def_id(closure.hir_id);
/// Walks into `ty` and returns `true` if any inner type is an instance of the given adt
/// constructor.
-pub fn contains_adt_constructor(ty: Ty<'_>, adt: &AdtDef) -> bool {
+pub fn contains_adt_constructor(ty: Ty<'_>, adt: AdtDef<'_>) -> bool {
ty.walk().any(|inner| match inner.unpack() {
GenericArgKind::Type(inner_ty) => inner_ty.ty_adt_def() == Some(adt),
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
let def_id = match ty_to_check.kind() {
ty::Array(..) => return Some(sym::array),
ty::Slice(..) => return Some(sym::slice),
- ty::Adt(adt, _) => adt.did,
+ ty::Adt(adt, _) => adt.did(),
_ => return None,
};
// Returns whether the type has #[must_use] attribute
pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
match ty.kind() {
- ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did)).is_some(),
+ ty::Adt(adt, _) => must_use_attr(cx.tcx.get_attrs(adt.did())).is_some(),
ty::Foreign(ref did) => must_use_attr(cx.tcx.get_attrs(*did)).is_some(),
ty::Slice(ty) | ty::Array(ty, _) | ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _) => {
// for the Array case we don't need to care for the len == 0 case
let cause = rustc_middle::traits::ObligationCause::dummy();
if infcx.at(&cause, param_env).normalize(ty).is_ok() {
match ty.kind() {
- ty::Adt(def, substs) => def.variants.iter().all(|variant| {
+ ty::Adt(def, substs) => def.variants().iter().all(|variant| {
variant
.fields
.iter()
pub fn is_type_ref_to_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
match ty.kind() {
ty::Ref(_, ref_ty, _) => match ref_ty.kind() {
- ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
_ => false,
},
_ => false,
/// [Diagnostic Items]: https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html
pub fn is_type_diagnostic_item(cx: &LateContext<'_>, ty: Ty<'_>, diag_item: Symbol) -> bool {
match ty.kind() {
- ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did),
+ ty::Adt(adt, _) => cx.tcx.is_diagnostic_item(diag_item, adt.did()),
_ => false,
}
}
/// 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,
}
}
/// If you change the signature, remember to update the internal lint `MatchTypeOnDiagItem`
pub fn match_type(cx: &LateContext<'_>, ty: Ty<'_>, path: &[&str]) -> bool {
match ty.kind() {
- ty::Adt(adt, _) => match_def_path(cx, adt.did, path),
+ ty::Adt(adt, _) => match_def_path(cx, adt.did(), path),
_ => false,
}
}
match *ty.kind() {
ty::Array(component, _) => is_uninit_value_valid_for_ty(cx, component),
ty::Tuple(types) => types.iter().all(|ty| is_uninit_value_valid_for_ty(cx, ty)),
- ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did),
+ ty::Adt(adt, _) => cx.tcx.lang_items().maybe_uninit() == Some(adt.did()),
_ => false,
}
}
}
/// Gets the value of the given variant.
-pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: &'_ AdtDef, i: VariantIdx) -> EnumValue {
- let variant = &adt.variants[i];
+pub fn get_discriminant_value(tcx: TyCtxt<'_>, adt: AdtDef<'_>, i: VariantIdx) -> EnumValue {
+ let variant = &adt.variant(i);
match variant.discr {
VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap(),
- VariantDiscr::Relative(x) => match adt.variants[(i.as_usize() - x as usize).into()].discr {
+ VariantDiscr::Relative(x) => match adt.variant((i.as_usize() - x as usize).into()).discr {
VariantDiscr::Explicit(id) => read_explicit_enum_value(tcx, id).unwrap() + x,
VariantDiscr::Relative(_) => EnumValue::Unsigned(x.into()),
},
/// platform specific `libc::<platform>::c_void` types in libc.
pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
if let ty::Adt(adt, _) = ty.kind()
- && let &[krate, .., name] = &*cx.get_def_path(adt.did)
+ && let &[krate, .., name] = &*cx.get_def_path(adt.did())
&& let sym::libc | sym::core | sym::std = krate
&& name.as_str() == "c_void"
{
[toolchain]
-channel = "nightly-2022-02-24"
+channel = "nightly-2022-03-24"
components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
"syn",
"tokio",
"parking_lot",
+ "rustc_semver",
];
// Test dependencies may need an `extern crate` here to ensure that they show up
#[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;
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);
}
fn new(path: PathBuf) -> Self {
let content: String = std::fs::read_to_string(&path).unwrap();
// we don't want the first letter after "error: ", "help: " ... to be capitalized
- // also no puncutation (except for "?" ?) at the end of a line
+ // also no punctuation (except for "?" ?) at the end of a line
let regex_set: RegexSet = RegexSet::new(&[
r"error: [A-Z]",
r"help: [A-Z]",
--- /dev/null
+// 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() {}
--- /dev/null
+// 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() {}
--- /dev/null
+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
+
--- /dev/null
+#![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() {}
--- /dev/null
+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
+
--- /dev/null
+#![warn(clippy::cast_enum_constructor)]
+#![allow(clippy::fn_to_numeric_cast)]
+
+fn main() {
+ enum Foo {
+ Y(u32),
+ }
+
+ enum Bar {
+ X,
+ }
+
+ let _ = Foo::Y as usize;
+ let _ = Foo::Y as isize;
+ let _ = Foo::Y as fn(u32) -> Foo;
+ let _ = Bar::X as usize;
+}
--- /dev/null
+error: cast of an enum tuple constructor to an integer
+ --> $DIR/cast_enum_constructor.rs:13:13
+ |
+LL | let _ = Foo::Y as usize;
+ | ^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::cast-enum-constructor` implied by `-D warnings`
+
+error: cast of an enum tuple constructor to an integer
+ --> $DIR/cast_enum_constructor.rs:14:13
+ |
+LL | let _ = Foo::Y as isize;
+ | ^^^^^^^^^^^^^^^
+
+error: aborting due to 2 previous errors
+
--- /dev/null
+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];
+}
--- /dev/null
+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
+
error[E0601]: `main` function not found in crate `ice_6250`
- --> $DIR/ice-6250.rs:4:1
+ --> $DIR/ice-6250.rs:16:2
|
-LL | / pub struct Cache {
-LL | | data: Vec<i32>,
-LL | | }
-LL | |
-... |
-LL | | }
-LL | | }
- | |_^ consider adding a `main` function to `$DIR/ice-6250.rs`
+LL | }
+ | ^ consider adding a `main` function to `$DIR/ice-6250.rs`
error[E0308]: mismatched types
--> $DIR/ice-6250.rs:12:14
error[E0601]: `main` function not found in crate `ice_6251`
- --> $DIR/ice-6251.rs:4:1
+ --> $DIR/ice-6251.rs:6:2
|
-LL | / fn bug<T>() -> impl Iterator<Item = [(); { |x: [u8]| x }]> {
-LL | | std::iter::empty()
-LL | | }
- | |_^ consider adding a `main` function to `$DIR/ice-6251.rs`
+LL | }
+ | ^ consider adding a `main` function to `$DIR/ice-6251.rs`
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
--> $DIR/ice-6251.rs:4:45
#[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
+// compile-flags: --test
#![warn(clippy::dbg_macro)]
fn foo(n: u32) -> u32 {
dbg!(2);
});
}
+
+#[test]
+pub fn issue8481() {
+ dbg!(1);
+}
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 }
| ^^^^^^^^^^^^^^^^^^^^^^
| ~~~~~~~~~~~~~~~~
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) {
| ^^^^^^^^^^^^
| ~~~~~~
error: `dbg!` macro is intended as a debugging tool
- --> $DIR/dbg_macro.rs:9:9
+ --> $DIR/dbg_macro.rs:10:9
|
LL | dbg!(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))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
error: `dbg!` macro is intended as a debugging tool
- --> $DIR/dbg_macro.rs:16:5
+ --> $DIR/dbg_macro.rs:17:5
|
LL | dbg!(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)));
| ^^^^^^^^^^^^^^^^^^^^
| ~~~~~~~~~~~~~~
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));
| ^^^^^^^^^^^^^^^^^^
| ~~~~~~~~~~~~
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));
| ^^^^^^^^^^^^^^^^^^^^^^
| ~~~~~~~~~~~~~~~~~~
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);
| ^^^^^^^^^^^^^^^^^^^
| ~~~~~~~~~~~~~~~
error: `dbg!` macro is intended as a debugging tool
- --> $DIR/dbg_macro.rs:40:9
+ --> $DIR/dbg_macro.rs:41:9
|
LL | dbg!(2);
| ^^^^^^^
// 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];
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();
// 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];
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();
let b = a << 0; // no error: non-integer
1 * Meter; // no error: non-integer
+
+ 2 % 3;
+ -2 % 3;
+ 2 % -3 + x;
+ -2 % -3 + x;
+ x + 1 % 3;
+ (x + 1) % 3; // no error
+ 4 % 3; // no error
+ 4 % -3; // no error
}
LL | x >> &0;
| ^^^^^^^
-error: aborting due to 13 previous errors
+error: the operation is ineffective. Consider reducing it to `2`
+ --> $DIR/identity_op.rs:70:5
+ |
+LL | 2 % 3;
+ | ^^^^^
+
+error: the operation is ineffective. Consider reducing it to `-2`
+ --> $DIR/identity_op.rs:71:5
+ |
+LL | -2 % 3;
+ | ^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `2`
+ --> $DIR/identity_op.rs:72:5
+ |
+LL | 2 % -3 + x;
+ | ^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `-2`
+ --> $DIR/identity_op.rs:73:5
+ |
+LL | -2 % -3 + x;
+ | ^^^^^^^
+
+error: the operation is ineffective. Consider reducing it to `1`
+ --> $DIR/identity_op.rs:74:9
+ |
+LL | x + 1 % 3;
+ | ^^^^^
+
+error: aborting due to 18 previous errors
--- /dev/null
+// 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());
+}
--- /dev/null
+// 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());
+}
--- /dev/null
+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
+
extern crate proc_macro_derive as mini_mac;
mod a {
- use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};
+ use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};
use mac;
use mini_mac::ClippyMiniMacroTest;
use mini_mac;
--> $DIR/macro_use_imports.rs:18:5
|
LL | #[macro_use]
- | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, inner_mod_macro, function_macro, ty_macro, pub_in_private_macro};`
+ | ^^^^^^^^^^^^ help: remove the attribute and import the macro directly, try: `use mac::{pub_macro, function_macro, ty_macro, inner_mod_macro, pub_in_private_macro};`
|
= note: `-D clippy::macro-use-imports` implied by `-D warnings`
// #7077
let s = &String::new();
+ #[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,
// #7077
let s = &String::new();
+ #[allow(clippy::needless_match)]
let _: Option<&str> = match Some(s) {
Some(s) => Some(s),
None => None,
}
// make sure parentheses are added properly to bitwise operators, which have lower precedence than
- // arithmetric ones
+ // arithmetic ones
let mut count = 0 << 1;
for i in 0..1 << 1 {
dst[count] = src[i + 2];
+++ /dev/null
-// run-rustfix
-
-#![warn(clippy::all, clippy::pedantic)]
-#![allow(clippy::let_underscore_drop)]
-#![allow(clippy::missing_docs_in_private_items)]
-#![allow(clippy::map_identity)]
-#![allow(clippy::redundant_closure)]
-#![allow(clippy::unnecessary_wraps)]
-#![feature(result_flattening)]
-
-fn main() {
- // mapping to Option on Iterator
- fn option_id(x: i8) -> Option<i8> {
- Some(x)
- }
- let option_id_ref: fn(i8) -> Option<i8> = option_id;
- let option_id_closure = |x| Some(x);
- let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
-
- // mapping to Iterator on Iterator
- let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
-
- // mapping to Option on Option
- let _: Option<_> = (Some(Some(1))).and_then(|x| x);
-
- // mapping to Result on Result
- let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
-}
-// run-rustfix
-
-#![warn(clippy::all, clippy::pedantic)]
-#![allow(clippy::let_underscore_drop)]
-#![allow(clippy::missing_docs_in_private_items)]
-#![allow(clippy::map_identity)]
-#![allow(clippy::redundant_closure)]
-#![allow(clippy::unnecessary_wraps)]
+#![warn(clippy::map_flatten)]
#![feature(result_flattening)]
-fn main() {
- // mapping to Option on Iterator
- fn option_id(x: i8) -> Option<i8> {
- Some(x)
- }
- let option_id_ref: fn(i8) -> Option<i8> = option_id;
- let option_id_closure = |x| Some(x);
- let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
- let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+// issue #8506, multi-line
+#[rustfmt::skip]
+fn long_span() {
+ let _: Option<i32> = Some(1)
+ .map(|x| {
+ if x <= 5 {
+ Some(x)
+ } else {
+ None
+ }
+ })
+ .flatten();
- // mapping to Iterator on Iterator
- let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+ let _: Result<i32, i32> = Ok(1)
+ .map(|x| {
+ if x == 1 {
+ Ok(x)
+ } else {
+ Err(0)
+ }
+ })
+ .flatten();
- // mapping to Option on Option
- let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+ let result: Result<i32, i32> = Ok(2);
+ fn do_something() { }
+ let _: Result<i32, i32> = result
+ .map(|res| {
+ if res > 0 {
+ do_something();
+ Ok(res)
+ } else {
+ Err(0)
+ }
+ })
+ .flatten();
+
+ let _: Vec<_> = vec![5_i8; 6]
+ .into_iter()
+ .map(|some_value| {
+ if some_value > 3 {
+ Some(some_value)
+ } else {
+ None
+ }
+ })
+ .flatten()
+ .collect();
+}
- // mapping to Result on Result
- let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+fn main() {
+ long_span();
}
-error: called `map(..).flatten()` on an `Iterator`
- --> $DIR/map_flatten.rs:18:46
+error: called `map(..).flatten()` on `Option`
+ --> $DIR/map_flatten.rs:8:10
|
-LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id)`
+LL | .map(|x| {
+ | __________^
+LL | | if x <= 5 {
+LL | | Some(x)
+LL | | } else {
+... |
+LL | | })
+LL | | .flatten();
+ | |__________________^
|
= note: `-D clippy::map-flatten` implied by `-D warnings`
-
-error: called `map(..).flatten()` on an `Iterator`
- --> $DIR/map_flatten.rs:19:46
+help: try replacing `map` with `and_then`
|
-LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_ref)`
-
-error: called `map(..).flatten()` on an `Iterator`
- --> $DIR/map_flatten.rs:20:46
+LL ~ .and_then(|x| {
+LL + if x <= 5 {
+LL + Some(x)
|
-LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(option_id_closure)`
-
-error: called `map(..).flatten()` on an `Iterator`
- --> $DIR/map_flatten.rs:21:46
+help: and remove the `.flatten()`
+ |
+LL + None
+LL + }
+LL ~ });
|
-LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `filter_map` instead: `.filter_map(|x| x.checked_add(1))`
-error: called `map(..).flatten()` on an `Iterator`
- --> $DIR/map_flatten.rs:24:46
+error: called `map(..).flatten()` on `Result`
+ --> $DIR/map_flatten.rs:18:10
+ |
+LL | .map(|x| {
+ | __________^
+LL | | if x == 1 {
+LL | | Ok(x)
+LL | | } else {
+... |
+LL | | })
+LL | | .flatten();
+ | |__________________^
+ |
+help: try replacing `map` with `and_then`
+ |
+LL ~ .and_then(|x| {
+LL + if x == 1 {
+LL + Ok(x)
+ |
+help: and remove the `.flatten()`
+ |
+LL + Err(0)
+LL + }
+LL ~ });
|
-LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try using `flat_map` instead: `.flat_map(|x| 0..x)`
-error: called `map(..).flatten()` on an `Option`
- --> $DIR/map_flatten.rs:27:39
+error: called `map(..).flatten()` on `Result`
+ --> $DIR/map_flatten.rs:30:10
+ |
+LL | .map(|res| {
+ | __________^
+LL | | if res > 0 {
+LL | | do_something();
+LL | | Ok(res)
+... |
+LL | | })
+LL | | .flatten();
+ | |__________________^
+ |
+help: try replacing `map` with `and_then`
+ |
+LL ~ .and_then(|res| {
+LL + if res > 0 {
+LL + do_something();
+ |
+help: and remove the `.flatten()`
+ |
+LL + Err(0)
+LL + }
+LL ~ });
|
-LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
- | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
-error: called `map(..).flatten()` on an `Result`
- --> $DIR/map_flatten.rs:30:41
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten.rs:42:10
+ |
+LL | .map(|some_value| {
+ | __________^
+LL | | if some_value > 3 {
+LL | | Some(some_value)
+LL | | } else {
+... |
+LL | | })
+LL | | .flatten()
+ | |__________________^
+ |
+help: try replacing `map` with `filter_map`
+ |
+LL ~ .filter_map(|some_value| {
+LL + if some_value > 3 {
+LL + Some(some_value)
+ |
+help: and remove the `.flatten()`
+ |
+LL + None
+LL + }
+LL + })
|
-LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
- | ^^^^^^^^^^^^^^^^^^^^^ help: try using `and_then` instead: `.and_then(|x| x)`
-error: aborting due to 7 previous errors
+error: aborting due to 4 previous errors
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::let_underscore_drop)]
+#![allow(clippy::missing_docs_in_private_items)]
+#![allow(clippy::map_identity)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::unnecessary_wraps)]
+#![feature(result_flattening)]
+
+fn main() {
+ // mapping to Option on Iterator
+ fn option_id(x: i8) -> Option<i8> {
+ Some(x)
+ }
+ let option_id_ref: fn(i8) -> Option<i8> = option_id;
+ let option_id_closure = |x| Some(x);
+ let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
+
+ // mapping to Iterator on Iterator
+ let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+
+ // mapping to Option on Option
+ let _: Option<_> = (Some(Some(1))).and_then(|x| x);
+
+ // mapping to Result on Result
+ let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::all, clippy::pedantic)]
+#![allow(clippy::let_underscore_drop)]
+#![allow(clippy::missing_docs_in_private_items)]
+#![allow(clippy::map_identity)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::unnecessary_wraps)]
+#![feature(result_flattening)]
+
+fn main() {
+ // mapping to Option on Iterator
+ fn option_id(x: i8) -> Option<i8> {
+ Some(x)
+ }
+ let option_id_ref: fn(i8) -> Option<i8> = option_id;
+ let option_id_closure = |x| Some(x);
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+
+ // mapping to Iterator on Iterator
+ let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+
+ // mapping to Option on Option
+ let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+
+ // mapping to Result on Result
+ let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+}
--- /dev/null
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten_fixable.rs:18:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `-D clippy::map-flatten` implied by `-D warnings`
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id).collect();
+ | ~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten_fixable.rs:19:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_ref).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_ref).collect();
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten_fixable.rs:20:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(option_id_closure).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(option_id_closure).collect();
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten_fixable.rs:21:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| x.checked_add(1)).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `filter_map`, and remove the `.flatten()`
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().filter_map(|x| x.checked_add(1)).collect();
+ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Iterator`
+ --> $DIR/map_flatten_fixable.rs:24:47
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().map(|x| 0..x).flatten().collect();
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `flat_map`, and remove the `.flatten()`
+ |
+LL | let _: Vec<_> = vec![5_i8; 6].into_iter().flat_map(|x| 0..x).collect();
+ | ~~~~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Option`
+ --> $DIR/map_flatten_fixable.rs:27:40
+ |
+LL | let _: Option<_> = (Some(Some(1))).map(|x| x).flatten();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `and_then`, and remove the `.flatten()`
+ |
+LL | let _: Option<_> = (Some(Some(1))).and_then(|x| x);
+ | ~~~~~~~~~~~~~~~
+
+error: called `map(..).flatten()` on `Result`
+ --> $DIR/map_flatten_fixable.rs:30:42
+ |
+LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten();
+ | ^^^^^^^^^^^^^^^^^^^^
+ |
+help: try replacing `map` with `and_then`, and remove the `.flatten()`
+ |
+LL | let _: Result<_, &str> = (Ok(Ok(1))).and_then(|x| x);
+ | ~~~~~~~~~~~~~~~
+
+error: aborting due to 7 previous errors
+
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:13:14
+error: this match arm has an identical body to the `_` wildcard arm
+ --> $DIR/match_same_arms.rs:11:9
|
-LL | _ => 0, //~ ERROR match arms have same body
- | ^
+LL | Abc::A => 0,
+ | ^^^^^^^^^^^ help: try removing the arm
|
= note: `-D clippy::match-same-arms` implied by `-D warnings`
-note: same as this
- --> $DIR/match_same_arms.rs:11:19
+ = help: or try changing either arm body
+note: `_` wildcard arm here
+ --> $DIR/match_same_arms.rs:13:9
|
-LL | Abc::A => 0,
- | ^
-note: `Abc::A` has the same arm body as the `_` wildcard, consider removing it
- --> $DIR/match_same_arms.rs:11:19
- |
-LL | Abc::A => 0,
- | ^
+LL | _ => 0, //~ ERROR match arms have same body
+ | ^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:18:20
- |
-LL | (.., 3) => 42, //~ ERROR match arms have same body
- | ^^
- |
-note: same as this
- --> $DIR/match_same_arms.rs:17:23
- |
-LL | (1, .., 3) => 42,
- | ^^
-help: consider refactoring into `(1, .., 3) | (.., 3)`
+error: this match arm has an identical body to another arm
--> $DIR/match_same_arms.rs:17:9
|
LL | (1, .., 3) => 42,
- | ^^^^^^^^^^
- = help: ...or consider changing the match arm bodies
+ | ----------^^^^^^
+ | |
+ | help: try merging the arm patterns: `(1, .., 3) | (.., 3)`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms.rs:18:9
+ |
+LL | (.., 3) => 42, //~ ERROR match arms have same body
+ | ^^^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:24:15
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms.rs:24:9
|
LL | 51 => 1, //~ ERROR match arms have same body
- | ^
+ | --^^^^^
+ | |
+ | help: try merging the arm patterns: `51 | 42`
|
-note: same as this
- --> $DIR/match_same_arms.rs:23:15
- |
-LL | 42 => 1,
- | ^
-help: consider refactoring into `42 | 51`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms.rs:23:9
|
LL | 42 => 1,
- | ^^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:26:15
- |
-LL | 52 => 2, //~ ERROR match arms have same body
- | ^
- |
-note: same as this
- --> $DIR/match_same_arms.rs:25:15
- |
-LL | 41 => 2,
- | ^
-help: consider refactoring into `41 | 52`
+error: this match arm has an identical body to another arm
--> $DIR/match_same_arms.rs:25:9
|
LL | 41 => 2,
- | ^^
- = help: ...or consider changing the match arm bodies
+ | --^^^^^
+ | |
+ | help: try merging the arm patterns: `41 | 52`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms.rs:26:9
+ |
+LL | 52 => 2, //~ ERROR match arms have same body
+ | ^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:32:14
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms.rs:32:9
|
LL | 2 => 2, //~ ERROR 2nd matched arms have same body
- | ^
- |
-note: same as this
- --> $DIR/match_same_arms.rs:31:14
+ | -^^^^^
+ | |
+ | help: try merging the arm patterns: `2 | 1`
|
-LL | 1 => 2,
- | ^
-help: consider refactoring into `1 | 2`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms.rs:31:9
|
LL | 1 => 2,
- | ^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:33:14
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms.rs:33:9
|
LL | 3 => 2, //~ ERROR 3rd matched arms have same body
- | ^
- |
-note: same as this
- --> $DIR/match_same_arms.rs:31:14
+ | -^^^^^
+ | |
+ | help: try merging the arm patterns: `3 | 1`
|
-LL | 1 => 2,
- | ^
-help: consider refactoring into `1 | 3`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms.rs:31:9
|
LL | 1 => 2,
- | ^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms.rs:50:55
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms.rs:32:9
|
-LL | CommandInfo::External { name, .. } => name.to_string(),
- | ^^^^^^^^^^^^^^^^
+LL | 2 => 2, //~ ERROR 2nd matched arms have same body
+ | -^^^^^
+ | |
+ | help: try merging the arm patterns: `2 | 3`
|
-note: same as this
- --> $DIR/match_same_arms.rs:49:54
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms.rs:33:9
|
-LL | CommandInfo::BuiltIn { name, .. } => name.to_string(),
- | ^^^^^^^^^^^^^^^^
-help: consider refactoring into `CommandInfo::BuiltIn { name, .. } | CommandInfo::External { name, .. }`
+LL | 3 => 2, //~ ERROR 3rd matched arms have same body
+ | ^^^^^^
+
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms.rs:50:17
+ |
+LL | CommandInfo::External { name, .. } => name.to_string(),
+ | ----------------------------------^^^^^^^^^^^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }`
+ |
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms.rs:49:17
|
LL | CommandInfo::BuiltIn { name, .. } => name.to_string(),
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: aborting due to 7 previous errors
+error: aborting due to 8 previous errors
Some(2) => 2,
_ => 1,
};
+
+ enum Foo {
+ X(u32),
+ Y(u32),
+ Z(u32),
+ }
+
+ // Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Suggest moving `Foo::Z(_)` up.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::X(_) | Foo::Y(_) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Suggest moving `Foo::X(0)` down.
+ let _ = match Foo::X(0) {
+ Foo::X(0) => 1,
+ Foo::Y(_) | Foo::Z(0) => 2,
+ Foo::Z(_) => 1,
+ _ => 0,
+ };
+
+ // Don't lint.
+ let _ = match 0 {
+ -2 => 1,
+ -5..=50 => 2,
+ -150..=88 => 1,
+ _ => 3,
+ };
+
+ struct Bar {
+ x: u32,
+ y: u32,
+ z: u32,
+ }
+
+ // Lint.
+ let _ = match None {
+ Some(Bar { x: 0, y: 5, .. }) => 1,
+ Some(Bar { y: 10, z: 0, .. }) => 2,
+ None => 50,
+ Some(Bar { y: 0, x: 5, .. }) => 1,
+ _ => 200,
+ };
}
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:20:14
+error: this match arm has an identical body to the `_` wildcard arm
+ --> $DIR/match_same_arms2.rs:11:9
|
-LL | _ => {
- | ______________^
-LL | | //~ ERROR match arms have same body
+LL | / 42 => {
LL | | foo();
LL | | let mut a = 42 + [23].len() as i32;
+LL | | if true {
... |
LL | | a
LL | | },
- | |_________^
+ | |_________^ help: try removing the arm
|
= note: `-D clippy::match-same-arms` implied by `-D warnings`
-note: same as this
- --> $DIR/match_same_arms2.rs:11:15
+ = help: or try changing either arm body
+note: `_` wildcard arm here
+ --> $DIR/match_same_arms2.rs:20:9
|
-LL | 42 => {
- | _______________^
-LL | | foo();
-LL | | let mut a = 42 + [23].len() as i32;
-LL | | if true {
-... |
-LL | | a
-LL | | },
- | |_________^
-note: `42` has the same arm body as the `_` wildcard, consider removing it
- --> $DIR/match_same_arms2.rs:11:15
- |
-LL | 42 => {
- | _______________^
+LL | / _ => {
+LL | | //~ ERROR match arms have same body
LL | | foo();
LL | | let mut a = 42 + [23].len() as i32;
-LL | | if true {
... |
LL | | a
LL | | },
| |_________^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:34:15
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:34:9
|
LL | 51 => foo(), //~ ERROR match arms have same body
- | ^^^^^
+ | --^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `51 | 42`
|
-note: same as this
- --> $DIR/match_same_arms2.rs:33:15
- |
-LL | 42 => foo(),
- | ^^^^^
-help: consider refactoring into `42 | 51`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms2.rs:33:9
|
LL | 42 => foo(),
- | ^^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:40:17
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:40:9
|
LL | None => 24, //~ ERROR match arms have same body
- | ^^
+ | ----^^^^^^
+ | |
+ | help: try merging the arm patterns: `None | Some(_)`
|
-note: same as this
- --> $DIR/match_same_arms2.rs:39:20
- |
-LL | Some(_) => 24,
- | ^^
-help: consider refactoring into `Some(_) | None`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms2.rs:39:9
|
LL | Some(_) => 24,
- | ^^^^^^^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:62:28
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:62:9
|
LL | (None, Some(a)) => bar(a), //~ ERROR match arms have same body
- | ^^^^^^
- |
-note: same as this
- --> $DIR/match_same_arms2.rs:61:28
+ | ---------------^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
|
-LL | (Some(a), None) => bar(a),
- | ^^^^^^
-help: consider refactoring into `(Some(a), None) | (None, Some(a))`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms2.rs:61:9
|
LL | (Some(a), None) => bar(a),
- | ^^^^^^^^^^^^^^^
- = help: ...or consider changing the match arm bodies
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:68:26
- |
-LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body
- | ^^^^^^
- |
-note: same as this
- --> $DIR/match_same_arms2.rs:67:26
- |
-LL | (Some(a), ..) => bar(a),
- | ^^^^^^
-help: consider refactoring into `(Some(a), ..) | (.., Some(a))`
+error: this match arm has an identical body to another arm
--> $DIR/match_same_arms2.rs:67:9
|
LL | (Some(a), ..) => bar(a),
- | ^^^^^^^^^^^^^
- = help: ...or consider changing the match arm bodies
-
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:102:29
- |
-LL | (Ok(_), Some(x)) => println!("ok {}", x),
- | ^^^^^^^^^^^^^^^^^^^^
+ | -------------^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))`
|
-note: same as this
- --> $DIR/match_same_arms2.rs:101:29
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:68:9
|
-LL | (Ok(x), Some(_)) => println!("ok {}", x),
- | ^^^^^^^^^^^^^^^^^^^^
-help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))`
+LL | (.., Some(a)) => bar(a), //~ ERROR match arms have same body
+ | ^^^^^^^^^^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
--> $DIR/match_same_arms2.rs:101:9
|
LL | (Ok(x), Some(_)) => println!("ok {}", x),
- | ^^^^^^^^^^^^^^^^
- = help: ...or consider changing the match arm bodies
- = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
+ | ----------------^^^^^^^^^^^^^^^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:102:9
+ |
+LL | (Ok(_), Some(x)) => println!("ok {}", x),
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:117:18
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:117:9
|
LL | Ok(_) => println!("ok"),
- | ^^^^^^^^^^^^^^
- |
-note: same as this
- --> $DIR/match_same_arms2.rs:116:18
+ | -----^^^^^^^^^^^^^^^^^^
+ | |
+ | help: try merging the arm patterns: `Ok(_) | Ok(3)`
|
-LL | Ok(3) => println!("ok"),
- | ^^^^^^^^^^^^^^
-help: consider refactoring into `Ok(3) | Ok(_)`
+ = help: or try changing either arm body
+note: other arm here
--> $DIR/match_same_arms2.rs:116:9
|
LL | Ok(3) => println!("ok"),
- | ^^^^^
- = help: ...or consider changing the match arm bodies
- = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
+ | ^^^^^^^^^^^^^^^^^^^^^^^
-error: this `match` has identical arm bodies
- --> $DIR/match_same_arms2.rs:144:14
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:144:9
|
LL | 1 => {
- | ______________^
+ | ^ help: try merging the arm patterns: `1 | 0`
+ | _________|
+ | |
LL | | empty!(0);
LL | | },
| |_________^
|
-note: same as this
- --> $DIR/match_same_arms2.rs:141:14
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:141:9
|
-LL | 0 => {
- | ______________^
+LL | / 0 => {
LL | | empty!(0);
LL | | },
| |_________^
-help: consider refactoring into `0 | 1`
- --> $DIR/match_same_arms2.rs:141:9
- |
-LL | 0 => {
- | ^
- = help: ...or consider changing the match arm bodies
error: match expression looks like `matches!` macro
--> $DIR/match_same_arms2.rs:162:16
|
= note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
-error: aborting due to 9 previous errors
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:194:9
+ |
+LL | Foo::X(0) => 1,
+ | ---------^^^^^
+ | |
+ | help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:196:9
+ |
+LL | Foo::Z(_) => 1,
+ | ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:204:9
+ |
+LL | Foo::Z(_) => 1,
+ | ---------^^^^^
+ | |
+ | help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:202:9
+ |
+LL | Foo::X(0) => 1,
+ | ^^^^^^^^^^^^^^
+
+error: this match arm has an identical body to another arm
+ --> $DIR/match_same_arms2.rs:227:9
+ |
+LL | Some(Bar { y: 0, x: 5, .. }) => 1,
+ | ----------------------------^^^^^
+ | |
+ | help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })`
+ |
+ = help: or try changing either arm body
+note: other arm here
+ --> $DIR/match_same_arms2.rs:224:9
+ |
+LL | Some(Bar { x: 0, y: 5, .. }) => 1,
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 12 previous errors
unsafe { transmute(&x) }
}
-// NOTE: This is currently not yet allowed to be const
-// Once implemented, Clippy should be able to suggest this as const, too.
fn generic_arr<T: Copy>(t: [T; 1]) -> T {
t[0]
}
| |_^
error: this could be a `const fn`
- --> $DIR/could_be_const.rs:67:9
+ --> $DIR/could_be_const.rs:52:1
+ |
+LL | / fn generic_arr<T: Copy>(t: [T; 1]) -> T {
+LL | | t[0]
+LL | | }
+ | |_^
+
+error: this could be a `const fn`
+ --> $DIR/could_be_const.rs:65:9
|
LL | / pub fn b(self, a: &A) -> B {
LL | | B
| |_________^
error: this could be a `const fn`
- --> $DIR/could_be_const.rs:77:5
+ --> $DIR/could_be_const.rs:75:5
|
LL | / fn const_fn_stabilized_before_msrv(byte: u8) {
LL | | byte.is_ascii_digit();
LL | | }
| |_____^
-error: aborting due to 9 previous errors
+error: aborting due to 10 previous errors
--- /dev/null
+// 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
+}
--- /dev/null
+// 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
+}
--- /dev/null
+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
+
--- /dev/null
+// 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() {}
--- /dev/null
+// 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() {}
--- /dev/null
+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
+
#![warn(clippy::modulo_arithmetic)]
-#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::modulo_one)]
+#![allow(
+ clippy::no_effect,
+ clippy::unnecessary_operation,
+ clippy::modulo_one,
+ clippy::identity_op
+)]
fn main() {
// Lint when both sides are const and of the opposite sign
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:6:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:11:5
|
LL | -1 % 2;
| ^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:7:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:12:5
|
LL | 1 % -2;
| ^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 3`
- --> $DIR/modulo_arithmetic_integral_const.rs:8:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:13:5
|
LL | (1 - 2) % (1 + 2);
| ^^^^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `3 % -1`
- --> $DIR/modulo_arithmetic_integral_const.rs:9:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:14:5
|
LL | (1 + 2) % (1 - 2);
| ^^^^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-35 % 300000`
- --> $DIR/modulo_arithmetic_integral_const.rs:10:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:15:5
|
LL | 35 * (7 - 4 * 2) % (-500 * -600);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:12:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:17:5
|
LL | -1i8 % 2i8;
| ^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:13:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:18:5
|
LL | 1i8 % -2i8;
| ^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:14:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:19:5
|
LL | -1i16 % 2i16;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:15:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:20:5
|
LL | 1i16 % -2i16;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:16:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:21:5
|
LL | -1i32 % 2i32;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:17:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:22:5
|
LL | 1i32 % -2i32;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:18:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:23:5
|
LL | -1i64 % 2i64;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:19:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:24:5
|
LL | 1i64 % -2i64;
| ^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:20:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:25:5
|
LL | -1i128 % 2i128;
| ^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:21:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:26:5
|
LL | 1i128 % -2i128;
| ^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `-1 % 2`
- --> $DIR/modulo_arithmetic_integral_const.rs:22:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:27:5
|
LL | -1isize % 2isize;
| ^^^^^^^^^^^^^^^^
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on constants with different signs: `1 % -2`
- --> $DIR/modulo_arithmetic_integral_const.rs:23:5
+ --> $DIR/modulo_arithmetic_integral_const.rs:28:5
|
LL | 1isize % -2isize;
| ^^^^^^^^^^^^^^^^
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
--> $DIR/modulo_one.rs:11:5
|
LL | i32::MIN % (-1); // also caught by rustc
| ^^^^^^^^^^^^^^^ attempt to compute the remainder of `i32::MIN % -1_i32`, which would overflow
|
- = note: `#[deny(arithmetic_overflow)]` on by default
+ = note: `#[deny(unconditional_panic)]` on by default
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
--> $DIR/modulo_one.rs:21:5
|
LL | INT_MIN % NEG_ONE; // also caught by rustc
| ^^^^^^^^^^^^^^^^^ attempt to compute the remainder of `i64::MIN % -1_i64`, which would overflow
-error: this arithmetic operation will overflow
+error: this operation will panic at runtime
--> $DIR/modulo_one.rs:22:5
|
LL | INT_MIN % STATIC_NEG_ONE; // ONLY caught by rustc
--- /dev/null
+// 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() {}
--- /dev/null
+// 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() {}
--- /dev/null
+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
+
--- /dev/null
+#![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() {}
--- /dev/null
+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
+
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::or_then_unwrap)]
+#![allow(clippy::map_identity)]
+
+struct SomeStruct {}
+impl SomeStruct {
+ fn or(self, _: Option<Self>) -> Self {
+ self
+ }
+ fn unwrap(&self) {}
+}
+
+struct SomeOtherStruct {}
+impl SomeOtherStruct {
+ fn or(self) -> Self {
+ self
+ }
+ fn unwrap(&self) {}
+}
+
+fn main() {
+ let option: Option<&str> = None;
+ let _ = option.unwrap_or("fallback"); // should trigger lint
+
+ let result: Result<&str, &str> = Err("Error");
+ let _ = result.unwrap_or("fallback"); // should trigger lint
+
+ // as part of a method chain
+ let option: Option<&str> = None;
+ let _ = option.map(|v| v).unwrap_or("fallback").to_string().chars(); // should trigger lint
+
+ // Not Option/Result
+ let instance = SomeStruct {};
+ let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
+
+ // or takes no argument
+ let instance = SomeOtherStruct {};
+ let _ = instance.or().unwrap(); // should not trigger lint and should not panic
+
+ // None in or
+ let option: Option<&str> = None;
+ let _ = option.or(None).unwrap(); // should not trigger lint
+
+ // Not Err in or
+ let result: Result<&str, &str> = Err("Error");
+ let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
+
+ // other function between
+ let option: Option<&str> = None;
+ let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::or_then_unwrap)]
+#![allow(clippy::map_identity)]
+
+struct SomeStruct {}
+impl SomeStruct {
+ fn or(self, _: Option<Self>) -> Self {
+ self
+ }
+ fn unwrap(&self) {}
+}
+
+struct SomeOtherStruct {}
+impl SomeOtherStruct {
+ fn or(self) -> Self {
+ self
+ }
+ fn unwrap(&self) {}
+}
+
+fn main() {
+ let option: Option<&str> = None;
+ let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
+
+ let result: Result<&str, &str> = Err("Error");
+ let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
+
+ // as part of a method chain
+ let option: Option<&str> = None;
+ let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
+
+ // Not Option/Result
+ let instance = SomeStruct {};
+ let _ = instance.or(Some(SomeStruct {})).unwrap(); // should not trigger lint
+
+ // or takes no argument
+ let instance = SomeOtherStruct {};
+ let _ = instance.or().unwrap(); // should not trigger lint and should not panic
+
+ // None in or
+ let option: Option<&str> = None;
+ let _ = option.or(None).unwrap(); // should not trigger lint
+
+ // Not Err in or
+ let result: Result<&str, &str> = Err("Error");
+ let _ = result.or::<&str>(Err("Other Error")).unwrap(); // should not trigger lint
+
+ // other function between
+ let option: Option<&str> = None;
+ let _ = option.or(Some("fallback")).map(|v| v).unwrap(); // should not trigger lint
+}
--- /dev/null
+error: found `.or(Some(…)).unwrap()`
+ --> $DIR/or_then_unwrap.rs:24:20
+ |
+LL | let _ = option.or(Some("fallback")).unwrap(); // should trigger lint
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+ |
+ = note: `-D clippy::or-then-unwrap` implied by `-D warnings`
+
+error: found `.or(Ok(…)).unwrap()`
+ --> $DIR/or_then_unwrap.rs:27:20
+ |
+LL | let _ = result.or::<&str>(Ok("fallback")).unwrap(); // should trigger lint
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+
+error: found `.or(Some(…)).unwrap()`
+ --> $DIR/or_then_unwrap.rs:31:31
+ |
+LL | let _ = option.map(|v| v).or(Some("fallback")).unwrap().to_string().chars(); // should trigger lint
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or("fallback")`
+
+error: aborting due to 3 previous errors
+
a.push(0);
b.push(1);
}
+
+// Issue #8495
+fn cow_conditional_to_mut(a: &mut Cow<str>) {
+ if a.is_empty() {
+ a.to_mut().push_str("foo");
+ }
+}
// run-rustfix
#![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}
// run-rustfix
#![allow(unused_parens)]
-
+#![allow(clippy::iter_with_drain)]
fn f() -> usize {
42
}
+++ /dev/null
-// run-rustfix
-#![warn(clippy::single_component_path_imports)]
-#![allow(unused_imports)]
-
-// #7106: use statements exporting a macro within a crate should not trigger lint
-
-macro_rules! m1 {
- () => {};
-}
-pub(crate) use m1; // ok
-
-macro_rules! m2 {
- () => {};
-}
- // fail
-
-fn main() {
- m1!();
- m2!();
-}
-// run-rustfix
#![warn(clippy::single_component_path_imports)]
#![allow(unused_imports)]
// #7106: use statements exporting a macro within a crate should not trigger lint
+// #7923: normal `use` statements of macros should also not trigger the lint
macro_rules! m1 {
() => {};
macro_rules! m2 {
() => {};
}
-use m2; // fail
+use m2; // ok
fn main() {
m1!();
+++ /dev/null
-error: this import is redundant
- --> $DIR/single_component_path_imports_macro.rs:15:1
- |
-LL | use m2; // fail
- | ^^^^^^^ help: remove it entirely
- |
- = note: `-D clippy::single-component-path-imports` implied by `-D warnings`
-
-error: aborting due to previous error
-
#![warn(clippy::transmute_undefined_repr)]
#![allow(clippy::unit_arg, clippy::transmute_ptr_to_ref)]
+use core::any::TypeId;
use core::ffi::c_void;
-use core::mem::{size_of, transmute};
+use core::mem::{size_of, transmute, MaybeUninit};
fn value<T>() -> T {
unimplemented!()
let _: *const [u8] = transmute(value::<Box<[u8]>>()); // Ok
let _: Box<[u8]> = transmute(value::<*mut [u8]>()); // Ok
+
+ let _: Ty2<u32, u32> = transmute(value::<(Ty2<u32, u32>,)>()); // Ok
+ let _: (Ty2<u32, u32>,) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+ let _: Ty2<u32, u32> = transmute(value::<(Ty2<u32, u32>, ())>()); // Ok
+ let _: (Ty2<u32, u32>, ()) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+ let _: Ty2<u32, u32> = transmute(value::<((), Ty2<u32, u32>)>()); // Ok
+ let _: ((), Ty2<u32, u32>) = transmute(value::<Ty2<u32, u32>>()); // Ok
+
+ let _: (usize, usize) = transmute(value::<&[u8]>()); // Ok
+ let _: &[u8] = transmute(value::<(usize, usize)>()); // Ok
+
+ trait Trait {}
+ let _: (isize, isize) = transmute(value::<&dyn Trait>()); // Ok
+ let _: &dyn Trait = transmute(value::<(isize, isize)>()); // Ok
+
+ let _: MaybeUninit<Ty2<u32, u32>> = transmute(value::<Ty2<u32, u32>>()); // Ok
+ let _: Ty2<u32, u32> = transmute(value::<MaybeUninit<Ty2<u32, u32>>>()); // Ok
+
+ let _: Ty<&[u32]> = transmute::<&[u32], _>(value::<&Vec<u32>>()); // Ok
+ }
+}
+
+fn _with_generics<T: 'static, U: 'static>() {
+ if TypeId::of::<T>() != TypeId::of::<u32>() || TypeId::of::<T>() != TypeId::of::<U>() {
+ return;
+ }
+ unsafe {
+ let _: &u32 = transmute(value::<&T>()); // Ok
+ let _: &T = transmute(value::<&u32>()); // Ok
+
+ let _: Vec<U> = transmute(value::<Vec<T>>()); // Ok
+ let _: Vec<T> = transmute(value::<Vec<U>>()); // Ok
+
+ let _: Ty<&u32> = transmute(value::<&T>()); // Ok
+ let _: Ty<&T> = transmute(value::<&u32>()); // Ok
+
+ let _: Vec<u32> = transmute(value::<Vec<T>>()); // Ok
+ let _: Vec<T> = transmute(value::<Vec<u32>>()); // Ok
+
+ let _: &Ty2<u32, u32> = transmute(value::<&Ty2<T, U>>()); // Ok
+ let _: &Ty2<T, U> = transmute(value::<&Ty2<u32, u32>>()); // Ok
+
+ let _: Vec<Vec<u32>> = transmute(value::<Vec<Vec<T>>>()); // Ok
+ let _: Vec<Vec<T>> = transmute(value::<Vec<Vec<u32>>>()); // Ok
+
+ let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); // Err
+ let _: Vec<Ty2<U, i32>> = transmute(value::<Vec<Ty2<T, u32>>>()); // Err
+
+ let _: *const u32 = transmute(value::<Box<T>>()); // Ok
+ let _: Box<T> = transmute(value::<*const u32>()); // Ok
}
}
error: transmute from `Ty2<u32, i32>` which has an undefined layout
- --> $DIR/transmute_undefined_repr.rs:26:33
+ --> $DIR/transmute_undefined_repr.rs:27:33
|
LL | let _: Ty2C<u32, i32> = transmute(value::<Ty2<u32, i32>>()); // Lint, Ty2 is unordered
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: `-D clippy::transmute-undefined-repr` implied by `-D warnings`
error: transmute into `Ty2<u32, i32>` which has an undefined layout
- --> $DIR/transmute_undefined_repr.rs:27:32
+ --> $DIR/transmute_undefined_repr.rs:28:32
|
LL | let _: Ty2<u32, i32> = transmute(value::<Ty2C<u32, i32>>()); // Lint, Ty2 is unordered
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: transmute from `Ty<Ty2<u32, i32>>` to `Ty2<u32, f32>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:32:32
+ --> $DIR/transmute_undefined_repr.rs:33:32
|
LL | let _: Ty2<u32, f32> = transmute(value::<Ty<Ty2<u32, i32>>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: two instances of the same generic type (`Ty2`) may have different layouts
error: transmute from `Ty2<u32, f32>` to `Ty<Ty2<u32, i32>>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:33:36
+ --> $DIR/transmute_undefined_repr.rs:34:36
|
LL | let _: Ty<Ty2<u32, i32>> = transmute(value::<Ty2<u32, f32>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: two instances of the same generic type (`Ty2`) may have different layouts
error: transmute from `Ty<&Ty2<u32, i32>>` to `&Ty2<u32, f32>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:38:33
+ --> $DIR/transmute_undefined_repr.rs:39:33
|
LL | let _: &Ty2<u32, f32> = transmute(value::<Ty<&Ty2<u32, i32>>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: two instances of the same generic type (`Ty2`) may have different layouts
error: transmute from `&Ty2<u32, f32>` to `Ty<&Ty2<u32, i32>>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:39:37
+ --> $DIR/transmute_undefined_repr.rs:40:37
|
LL | let _: Ty<&Ty2<u32, i32>> = transmute(value::<&Ty2<u32, f32>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: two instances of the same generic type (`Ty2`) may have different layouts
error: transmute from `std::boxed::Box<Ty2<u32, u32>>` to `&mut Ty2<u32, f32>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:56:45
+ --> $DIR/transmute_undefined_repr.rs:57:45
|
LL | let _: &'static mut Ty2<u32, f32> = transmute(value::<Box<Ty2<u32, u32>>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: two instances of the same generic type (`Ty2`) may have different layouts
error: transmute from `&mut Ty2<u32, f32>` to `std::boxed::Box<Ty2<u32, u32>>`, both of which have an undefined layout
- --> $DIR/transmute_undefined_repr.rs:57:37
+ --> $DIR/transmute_undefined_repr.rs:58:37
|
LL | let _: Box<Ty2<u32, u32>> = transmute(value::<&'static mut Ty2<u32, f32>>()); // Lint, different Ty2 instances
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: two instances of the same generic type (`Ty2`) may have different layouts
-error: aborting due to 8 previous errors
+error: transmute from `std::vec::Vec<Ty2<U, i32>>` to `std::vec::Vec<Ty2<T, u32>>`, both of which have an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:138:35
+ |
+LL | let _: Vec<Ty2<T, u32>> = transmute(value::<Vec<Ty2<U, i32>>>()); // Err
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: two instances of the same generic type (`Vec`) may have different layouts
+
+error: transmute from `std::vec::Vec<Ty2<T, u32>>` to `std::vec::Vec<Ty2<U, i32>>`, both of which have an undefined layout
+ --> $DIR/transmute_undefined_repr.rs:139:35
+ |
+LL | let _: Vec<Ty2<U, i32>> = transmute(value::<Vec<Ty2<T, u32>>>()); // Err
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: two instances of the same generic type (`Vec`) may have different layouts
+
+error: aborting due to 10 previous errors
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?
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?
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
LL | let _array_ptr_transmute = unsafe { transmute::<&[i32; 4], *const [i32; 4]>(array_ref) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `array_ref as *const [i32; 4]`
-error: transmute from `fn(usize) -> u8 {main::foo}` to `*const usize` which could be expressed as a pointer cast instead
+error: transmute from `fn(usize) -> u8` to `*const usize` which could be expressed as a pointer cast instead
--> $DIR/transmutes_expressible_as_ptr_casts.rs:48:41
|
LL | let _usize_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, *const usize>(foo) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `foo as *const usize`
-error: transmute from `fn(usize) -> u8 {main::foo}` to `usize` which could be expressed as a pointer cast instead
+error: transmute from `fn(usize) -> u8` to `usize` which could be expressed as a pointer cast instead
--> $DIR/transmutes_expressible_as_ptr_casts.rs:52:49
|
LL | let _usize_from_fn_ptr_transmute = unsafe { transmute::<fn(usize) -> u8, usize>(foo) };
--- /dev/null
+#![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)
+}
--- /dev/null
+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
+
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::unnecessary_join)]
+
+fn main() {
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<String>();
+ println!("{}", output);
+
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<String>();
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("\n");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ println!("{}", output);
+}
--- /dev/null
+// run-rustfix
+
+#![warn(clippy::unnecessary_join)]
+
+fn main() {
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("");
+ println!("{}", output);
+
+ // should be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<_>>()
+ .join("");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector
+ .iter()
+ .map(|item| item.to_uppercase())
+ .collect::<Vec<String>>()
+ .join("\n");
+ println!("{}", output);
+
+ // should not be linted
+ let vector = vec!["hello", "world"];
+ let output = vector.iter().map(|item| item.to_uppercase()).collect::<String>();
+ println!("{}", output);
+}
--- /dev/null
+error: called `.collect<Vec<String>>().join("")` on an iterator
+ --> $DIR/unnecessary_join.rs:11:10
+ |
+LL | .collect::<Vec<String>>()
+ | __________^
+LL | | .join("");
+ | |_________________^ help: try using: `collect::<String>()`
+ |
+ = note: `-D clippy::unnecessary-join` implied by `-D warnings`
+
+error: called `.collect<Vec<String>>().join("")` on an iterator
+ --> $DIR/unnecessary_join.rs:20:10
+ |
+LL | .collect::<Vec<_>>()
+ | __________^
+LL | | .join("");
+ | |_________________^ help: try using: `collect::<String>()`
+
+error: aborting due to 2 previous errors
+
let _: Result<usize, usize> = res.or(Ok(2));
let _: Result<usize, usize> = res.or(Ok(astronomers_pi));
let _: Result<usize, usize> = res.or(Ok(ext_str.some_field));
+ let _: Result<usize, usize> = res.
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ or(Ok(ext_str.some_field));
// neither bind_instead_of_map nor unnecessary_lazy_eval applies here
let _: Result<usize, usize> = res.and_then(|x| Err(x));
let _: Result<usize, usize> = res.or_else(|_| Ok(2));
let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
+ let _: Result<usize, usize> = res.
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ // some lines
+ or_else(|_| Ok(ext_str.some_field));
// neither bind_instead_of_map nor unnecessary_lazy_eval applies here
let _: Result<usize, usize> = res.and_then(|x| Err(x));
--> $DIR/unnecessary_lazy_eval.rs:35:13
|
LL | let _ = opt.unwrap_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(2)`
+ | ^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
|
= note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
--> $DIR/unnecessary_lazy_eval.rs:36:13
|
LL | let _ = opt.unwrap_or_else(|| astronomers_pi);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(astronomers_pi)`
+ | ^^^^---------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:37:13
|
LL | let _ = opt.unwrap_or_else(|| ext_str.some_field);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `opt.unwrap_or(ext_str.some_field)`
+ | ^^^^-------------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:39:13
|
LL | let _ = opt.and_then(|_| ext_opt);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `opt.and(ext_opt)`
+ | ^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:40:13
|
LL | let _ = opt.or_else(|| ext_opt);
- | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(ext_opt)`
+ | ^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(ext_opt)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:41:13
|
LL | let _ = opt.or_else(|| None);
- | ^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(None)`
+ | ^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:42:13
|
LL | let _ = opt.get_or_insert_with(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `opt.get_or_insert(2)`
+ | ^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:43:13
|
LL | let _ = opt.ok_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `opt.ok_or(2)`
+ | ^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:44:13
|
LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2)));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `nested_tuple_opt.unwrap_or(Some((1, 2)))`
+ | ^^^^^^^^^^^^^^^^^-------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:47:13
|
LL | let _ = Some(10).unwrap_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Some(10).unwrap_or(2)`
+ | ^^^^^^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:48:13
|
LL | let _ = Some(10).and_then(|_| ext_opt);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `Some(10).and(ext_opt)`
+ | ^^^^^^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:49:28
|
LL | let _: Option<usize> = None.or_else(|| ext_opt);
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(ext_opt)`
+ | ^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(ext_opt)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:50:13
|
LL | let _ = None.get_or_insert_with(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `None.get_or_insert(2)`
+ | ^^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:51:35
|
LL | let _: Result<usize, usize> = None.ok_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `None.ok_or(2)`
+ | ^^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:52:28
|
LL | let _: Option<usize> = None.or_else(|| None);
- | ^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(None)`
+ | ^^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:55:13
|
LL | let _ = deep.0.unwrap_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `deep.0.unwrap_or(2)`
+ | ^^^^^^^--------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:56:13
|
LL | let _ = deep.0.and_then(|_| ext_opt);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `deep.0.and(ext_opt)`
+ | ^^^^^^^---------------------
+ | |
+ | help: use `and(..)` instead: `and(ext_opt)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:57:13
|
LL | let _ = deep.0.or_else(|| None);
- | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(None)`
+ | ^^^^^^^----------------
+ | |
+ | help: use `or(..)` instead: `or(None)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:58:13
|
LL | let _ = deep.0.get_or_insert_with(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `get_or_insert` instead: `deep.0.get_or_insert(2)`
+ | ^^^^^^^------------------------
+ | |
+ | help: use `get_or_insert(..)` instead: `get_or_insert(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:59:13
|
LL | let _ = deep.0.ok_or_else(|| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ok_or` instead: `deep.0.ok_or(2)`
+ | ^^^^^^^----------------
+ | |
+ | help: use `ok_or(..)` instead: `ok_or(2)`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:79:28
|
LL | let _: Option<usize> = None.or_else(|| Some(3));
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `None.or(Some(3))`
+ | ^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:80:13
|
LL | let _ = deep.0.or_else(|| Some(3));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `deep.0.or(Some(3))`
+ | ^^^^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
error: unnecessary closure used to substitute value for `Option::None`
--> $DIR/unnecessary_lazy_eval.rs:81:13
|
LL | let _ = opt.or_else(|| Some(3));
- | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `opt.or(Some(3))`
+ | ^^^^-------------------
+ | |
+ | help: use `or(..)` instead: `or(Some(3))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:87:13
|
LL | let _ = res2.unwrap_or_else(|_| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(2)`
+ | ^^^^^---------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:88:13
|
LL | let _ = res2.unwrap_or_else(|_| astronomers_pi);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(astronomers_pi)`
+ | ^^^^^----------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:89:13
|
LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `res2.unwrap_or(ext_str.some_field)`
+ | ^^^^^--------------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:111:35
|
LL | let _: Result<usize, usize> = res.and_then(|_| Err(2));
- | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(2))`
+ | ^^^^--------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(2))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:112:35
|
LL | let _: Result<usize, usize> = res.and_then(|_| Err(astronomers_pi));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(astronomers_pi))`
+ | ^^^^---------------------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(astronomers_pi))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:113:35
|
LL | let _: Result<usize, usize> = res.and_then(|_| Err(ext_str.some_field));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `and` instead: `res.and(Err(ext_str.some_field))`
+ | ^^^^-------------------------------------
+ | |
+ | help: use `and(..)` instead: `and(Err(ext_str.some_field))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:115:35
|
LL | let _: Result<usize, usize> = res.or_else(|_| Ok(2));
- | ^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(2))`
+ | ^^^^------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(2))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:116:35
|
LL | let _: Result<usize, usize> = res.or_else(|_| Ok(astronomers_pi));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(astronomers_pi))`
+ | ^^^^-------------------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(astronomers_pi))`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval.rs:117:35
|
LL | let _: Result<usize, usize> = res.or_else(|_| Ok(ext_str.some_field));
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `or` instead: `res.or(Ok(ext_str.some_field))`
+ | ^^^^-----------------------------------
+ | |
+ | help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
-error: aborting due to 32 previous errors
+error: unnecessary closure used to substitute value for `Result::Err`
+ --> $DIR/unnecessary_lazy_eval.rs:118:35
+ |
+LL | let _: Result<usize, usize> = res.
+ | ___________________________________^
+LL | | // some lines
+LL | | // some lines
+LL | | // some lines
+... |
+LL | | // some lines
+LL | | or_else(|_| Ok(ext_str.some_field));
+ | |_________----------------------------------^
+ | |
+ | help: use `or(..)` instead: `or(Ok(ext_str.some_field))`
+
+error: aborting due to 33 previous errors
--> $DIR/unnecessary_lazy_eval_unfixable.rs:12:13
|
LL | let _ = Ok(1).unwrap_or_else(|()| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+ | ^^^^^^----------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
|
= note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings`
--> $DIR/unnecessary_lazy_eval_unfixable.rs:16:13
|
LL | let _ = Ok(1).unwrap_or_else(|e::E| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+ | ^^^^^^------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
error: unnecessary closure used to substitute value for `Result::Err`
--> $DIR/unnecessary_lazy_eval_unfixable.rs:17:13
|
LL | let _ = Ok(1).unwrap_or_else(|SomeStruct { .. }| 2);
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `unwrap_or` instead: `Ok(1).unwrap_or(2)`
+ | ^^^^^^-------------------------------------
+ | |
+ | help: use `unwrap_or(..)` instead: `unwrap_or(2)`
error: aborting due to 3 previous errors
}
fn require_string(_: &String) {}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+ #![allow(dead_code)]
+
+ struct Opaque<P>(P);
+
+ pub trait Abstracted {}
+
+ impl<P> Abstracted for Opaque<P> {}
+
+ fn build<P>(p: P) -> Opaque<P>
+ where
+ P: AsRef<str>,
+ {
+ Opaque(p)
+ }
+
+ // Should not lint.
+ fn test_str(s: &str) -> Box<dyn Abstracted> {
+ Box::new(build(s.to_string()))
+ }
+
+ // Should not lint.
+ fn test_x(x: super::X) -> Box<dyn Abstracted> {
+ Box::new(build(x))
+ }
+
+ #[derive(Clone, Copy)]
+ struct Y(&'static str);
+
+ impl AsRef<str> for Y {
+ fn as_ref(&self) -> &str {
+ self.0
+ }
+ }
+
+ impl ToString for Y {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+ }
+
+ // Should lint because Y is copy.
+ fn test_y(y: Y) -> Box<dyn Abstracted> {
+ Box::new(build(y))
+ }
+}
}
fn require_string(_: &String) {}
+
+// https://github.com/rust-lang/rust-clippy/issues/8507
+mod issue_8507 {
+ #![allow(dead_code)]
+
+ struct Opaque<P>(P);
+
+ pub trait Abstracted {}
+
+ impl<P> Abstracted for Opaque<P> {}
+
+ fn build<P>(p: P) -> Opaque<P>
+ where
+ P: AsRef<str>,
+ {
+ Opaque(p)
+ }
+
+ // Should not lint.
+ fn test_str(s: &str) -> Box<dyn Abstracted> {
+ Box::new(build(s.to_string()))
+ }
+
+ // Should not lint.
+ fn test_x(x: super::X) -> Box<dyn Abstracted> {
+ Box::new(build(x))
+ }
+
+ #[derive(Clone, Copy)]
+ struct Y(&'static str);
+
+ impl AsRef<str> for Y {
+ fn as_ref(&self) -> &str {
+ self.0
+ }
+ }
+
+ impl ToString for Y {
+ fn to_string(&self) -> String {
+ self.0.to_string()
+ }
+ }
+
+ // Should lint because Y is copy.
+ fn test_y(y: Y) -> Box<dyn Abstracted> {
+ Box::new(build(y.to_string()))
+ }
+}
LL + let path = match get_file_path(t) {
|
-error: aborting due to 76 previous errors
+error: unnecessary use of `to_string`
+ --> $DIR/unnecessary_to_owned.rs:260:24
+ |
+LL | Box::new(build(y.to_string()))
+ | ^^^^^^^^^^^^^ help: use: `y`
+
+error: aborting due to 77 previous errors
// 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,
}
}
}
+
+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!()
+ }
+ }
+ }
+}
// 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,
}
}
}
+
+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!()
+ }
+ }
+ }
+}
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
output
};
- // Trigger a sucessful build, so Cargo would like to cache the build result.
+ // Trigger a successful build, so Cargo would like to cache the build result.
successful_build();
// Make sure there's no spurious rebuild when nothing changes.
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; }
.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;
--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;
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);
}
</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>
-
- <div class="container" ng-app="clippy" ng-controller="lintList">
+<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">
<div class="page-header">
<h1>Clippy Lints</h1>
</div>
</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 class="panel-body row">
+ <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>
- <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 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>
- <div class="panel-body row">
- <div class="col-md-12 form-horizontal">
+ <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}"/>
</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 -->
</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>
);
};
})
+ .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',
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];
};
}
}
- 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;