Merge commit '60cb29c5e4f9772685c9873752196725c946a849' into clippyup

This commit is contained in:
Philipp Krones 2024-02-08 20:24:42 +01:00
parent 7a827028a5
commit f3b3d23416
131 changed files with 3886 additions and 621 deletions

15
.github/driver.sh vendored Normal file → Executable file
View file

@ -11,9 +11,16 @@ if [[ ${OS} == "Windows" ]]; then
else else
desired_sysroot=/tmp desired_sysroot=/tmp
fi fi
# Set --sysroot in command line
sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot) sysroot=$(./target/debug/clippy-driver --sysroot $desired_sysroot --print sysroot)
test "$sysroot" = $desired_sysroot test "$sysroot" = $desired_sysroot
# Set --sysroot in arg_file.txt and pass @arg_file.txt to command line
echo "--sysroot=$desired_sysroot" > arg_file.txt
sysroot=$(./target/debug/clippy-driver @arg_file.txt --print sysroot)
test "$sysroot" = $desired_sysroot
# Setting SYSROOT in command line
sysroot=$(SYSROOT=$desired_sysroot ./target/debug/clippy-driver --print sysroot) sysroot=$(SYSROOT=$desired_sysroot ./target/debug/clippy-driver --print sysroot)
test "$sysroot" = $desired_sysroot test "$sysroot" = $desired_sysroot
@ -24,6 +31,14 @@ test "$sysroot" = $desired_sysroot
SYSROOT=/tmp RUSTFLAGS="--sysroot=$(rustc --print sysroot)" ../target/debug/cargo-clippy clippy --verbose SYSROOT=/tmp RUSTFLAGS="--sysroot=$(rustc --print sysroot)" ../target/debug/cargo-clippy clippy --verbose
) )
# Check that the --sysroot argument is only passed once via arg_file.txt (SYSROOT is ignored)
(
echo "fn main() {}" > target/driver_test.rs
echo "--sysroot="$(./target/debug/clippy-driver --print sysroot)"" > arg_file.txt
echo "--verbose" >> arg_file.txt
SYSROOT=/tmp ./target/debug/clippy-driver @arg_file.txt ./target/driver_test.rs
)
# Make sure this isn't set - clippy-driver should cope without it # Make sure this isn't set - clippy-driver should cope without it
unset CARGO_MANIFEST_DIR unset CARGO_MANIFEST_DIR

View file

@ -6,11 +6,65 @@ document.
## Unreleased / Beta / In Rust Nightly ## Unreleased / Beta / In Rust Nightly
[09ac14c9...master](https://github.com/rust-lang/rust-clippy/compare/09ac14c9...master) [a859e5cc...master](https://github.com/rust-lang/rust-clippy/compare/a859e5cc...master)
## Rust 1.76
Current stable, released 2024-02-08
[View all 85 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-11-02T20%3A23%3A40Z..2023-12-16T13%3A11%3A08Z+base%3Amaster)
### New Lints
- [`infinite_loop`]
[#11829](https://github.com/rust-lang/rust-clippy/pull/11829)
- [`ineffective_open_options`]
[#11902](https://github.com/rust-lang/rust-clippy/pull/11902)
- [`uninhabited_references`]
[#11878](https://github.com/rust-lang/rust-clippy/pull/11878)
- [`repeat_vec_with_capacity`]
[#11597](https://github.com/rust-lang/rust-clippy/pull/11597)
- [`test_attr_in_doctest`]
[#11872](https://github.com/rust-lang/rust-clippy/pull/11872)
- [`option_map_or_err_ok`]
[#11864](https://github.com/rust-lang/rust-clippy/pull/11864)
- [`join_absolute_paths`]
[#11453](https://github.com/rust-lang/rust-clippy/pull/11453)
- [`impl_hash_borrow_with_str_and_bytes`]
[#11781](https://github.com/rust-lang/rust-clippy/pull/11781)
- [`iter_over_hash_type`]
[#11791](https://github.com/rust-lang/rust-clippy/pull/11791)
### Moves and Deprecations
- Renamed `blocks_in_if_conditions` to [`blocks_in_conditions`]
[#11853](https://github.com/rust-lang/rust-clippy/pull/11853)
- Moved [`implied_bounds_in_impls`] to `complexity` (Now warn-by-default)
[#11867](https://github.com/rust-lang/rust-clippy/pull/11867)
- Moved [`if_same_then_else`] to `style` (Now warn-by-default)
[#11809](https://github.com/rust-lang/rust-clippy/pull/11809)
### Enhancements
- [`missing_safety_doc`], [`unnecessary_safety_doc`], [`missing_panics_doc`], [`missing_errors_doc`]:
Added the [`check-private-items`] configuration to enable lints on private items
[#11842](https://github.com/rust-lang/rust-clippy/pull/11842)
### ICE Fixes
- [`impl_trait_in_params`]: No longer crashes when a function has generics but no function parameters
[#11804](https://github.com/rust-lang/rust-clippy/pull/11804)
- [`unused_enumerate_index`]: No longer crashes on empty tuples
[#11756](https://github.com/rust-lang/rust-clippy/pull/11756)
### Others
- Clippy now respects the `CARGO` environment value
[#11944](https://github.com/rust-lang/rust-clippy/pull/11944)
## Rust 1.75 ## Rust 1.75
Current stable, released 2023-12-28 Released 2023-12-28
[View all 69 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-09-25T11%3A47%3A47Z..2023-11-02T16%3A41%3A59Z+base%3Amaster) [View all 69 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2023-09-25T11%3A47%3A47Z..2023-11-02T16%3A41%3A59Z+base%3Amaster)
@ -5198,6 +5252,7 @@ Released 2018-09-13
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls [`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons [`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops [`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping [`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor [`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type [`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type
@ -5276,6 +5331,7 @@ Released 2018-09-13
[`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore [`let_with_type_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#let_with_type_underscore
[`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok [`lines_filter_map_ok`]: https://rust-lang.github.io/rust-clippy/master/index.html#lines_filter_map_ok
[`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist [`linkedlist`]: https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist
[`lint_groups_priority`]: https://rust-lang.github.io/rust-clippy/master/index.html#lint_groups_priority
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes [`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug [`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal [`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
@ -5284,6 +5340,7 @@ Released 2018-09-13
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert [`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp [`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter [`manual_filter`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map [`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
@ -5523,6 +5580,7 @@ Released 2018-09-13
[`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing [`redundant_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_slicing
[`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes [`redundant_static_lifetimes`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes
[`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations [`redundant_type_annotations`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_type_annotations
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference [`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref [`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref [`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
@ -5622,6 +5680,7 @@ Released 2018-09-13
[`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some [`to_digit_is_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_digit_is_some
[`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display [`to_string_in_display`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_display
[`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args [`to_string_in_format_args`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_in_format_args
[`to_string_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#to_string_trait_impl
[`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo [`todo`]: https://rust-lang.github.io/rust-clippy/master/index.html#todo
[`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments [`too_many_arguments`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
[`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines [`too_many_lines`]: https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines
@ -5677,6 +5736,7 @@ Released 2018-09-13
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed [`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 [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings [`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings
[`unnecessary_result_map_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_result_map_or_else
[`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment [`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment
[`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc [`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc
[`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports [`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports
@ -5819,4 +5879,6 @@ Released 2018-09-13
[`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow [`enforce-iter-loop-reborrow`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enforce-iter-loop-reborrow
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items [`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
[`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior [`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior
[`allow-comparison-to-zero`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-comparison-to-zero
[`allowed-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-wildcard-imports
<!-- end autogenerated links to configuration documentation --> <!-- end autogenerated links to configuration documentation -->

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy" name = "clippy"
version = "0.1.77" version = "0.1.78"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"

View file

@ -151,6 +151,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold) * [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
* [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one) * [`manual_hash_one`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one)
* [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map) * [`iter_kv_map`](https://rust-lang.github.io/rust-clippy/master/index.html#iter_kv_map)
* [`manual_c_str_literals`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals)
## `cognitive-complexity-threshold` ## `cognitive-complexity-threshold`
@ -828,3 +829,35 @@ exported visibility, or whether they are marked as "pub".
* [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields) * [`pub_underscore_fields`](https://rust-lang.github.io/rust-clippy/master/index.html#pub_underscore_fields)
## `allow-comparison-to-zero`
Don't lint when comparing the result of a modulo operation to zero.
**Default Value:** `true`
---
**Affected lints:**
* [`modulo_arithmetic`](https://rust-lang.github.io/rust-clippy/master/index.html#modulo_arithmetic)
## `allowed-wildcard-imports`
List of path segments allowed to have wildcard imports.
#### Example
```toml
allowed-wildcard-imports = [ "utils", "common" ]
```
#### Noteworthy
1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
2. Paths with any segment that containing the word 'prelude'
are already allowed by default.
**Default Value:** `[]`
---
**Affected lints:**
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_config" name = "clippy_config"
version = "0.1.77" version = "0.1.78"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -260,7 +260,7 @@ define_Conf! {
/// ///
/// Suppress lints whenever the suggested change would cause breakage for other crates. /// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true), (avoid_breaking_exported_api: bool = true),
/// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP. /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS.
/// ///
/// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml`
#[default_text = ""] #[default_text = ""]
@ -567,6 +567,26 @@ define_Conf! {
/// Lint "public" fields in a struct that are prefixed with an underscore based on their /// Lint "public" fields in a struct that are prefixed with an underscore based on their
/// exported visibility, or whether they are marked as "pub". /// exported visibility, or whether they are marked as "pub".
(pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported), (pub_underscore_fields_behavior: PubUnderscoreFieldsBehaviour = PubUnderscoreFieldsBehaviour::PubliclyExported),
/// Lint: MODULO_ARITHMETIC.
///
/// Don't lint when comparing the result of a modulo operation to zero.
(allow_comparison_to_zero: bool = true),
/// Lint: WILDCARD_IMPORTS.
///
/// List of path segments allowed to have wildcard imports.
///
/// #### Example
///
/// ```toml
/// allowed-wildcard-imports = [ "utils", "common" ]
/// ```
///
/// #### Noteworthy
///
/// 1. This configuration has no effects if used with `warn_on_all_wildcard_imports = true`.
/// 2. Paths with any segment that containing the word 'prelude'
/// are already allowed by default.
(allowed_wildcard_imports: FxHashSet<String> = FxHashSet::default()),
} }
/// Search for the configuration file. /// Search for the configuration file.

View file

@ -3,6 +3,7 @@ use rustc_semver::RustcVersion;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::{sym, Symbol}; use rustc_span::{sym, Symbol};
use serde::Deserialize; use serde::Deserialize;
use std::fmt;
macro_rules! msrv_aliases { macro_rules! msrv_aliases {
($($major:literal,$minor:literal,$patch:literal { ($($major:literal,$minor:literal,$patch:literal {
@ -16,6 +17,8 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items // names may refer to stabilized feature flags or library items
msrv_aliases! { msrv_aliases! {
1,77,0 { C_STR_LITERALS }
1,76,0 { PTR_FROM_REF }
1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE }
1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN } 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN }
1,68,0 { PATH_MAIN_SEPARATOR_STR } 1,68,0 { PATH_MAIN_SEPARATOR_STR }
@ -58,6 +61,16 @@ pub struct Msrv {
stack: Vec<RustcVersion>, stack: Vec<RustcVersion>,
} }
impl fmt::Display for Msrv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(msrv) = self.current() {
write!(f, "{msrv}")
} else {
f.write_str("1.0.0")
}
}
}
impl<'de> Deserialize<'de> for Msrv { impl<'de> Deserialize<'de> for Msrv {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_lints" name = "clippy_lints"
version = "0.1.77" version = "0.1.78"
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md" readme = "README.md"

View file

@ -499,6 +499,7 @@ struct NotSimplificationVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> { impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind
&& !expr.span.from_expansion()
&& !inner.span.from_expansion() && !inner.span.from_expansion()
&& let Some(suggestion) = simplify_not(self.cx, inner) && let Some(suggestion) = simplify_not(self.cx, inner)
&& self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow && self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow

View file

@ -0,0 +1,168 @@
use super::LINT_GROUPS_PRIORITY;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_lint::{unerased_lint_store, LateContext};
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::ops::Range;
use std::path::Path;
use toml::Spanned;
#[derive(Deserialize, Serialize, Debug)]
struct LintConfigTable {
level: String,
priority: Option<i64>,
}
#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum LintConfig {
Level(String),
Table(LintConfigTable),
}
impl LintConfig {
fn level(&self) -> &str {
match self {
LintConfig::Level(level) => level,
LintConfig::Table(table) => &table.level,
}
}
fn priority(&self) -> i64 {
match self {
LintConfig::Level(_) => 0,
LintConfig::Table(table) => table.priority.unwrap_or(0),
}
}
fn is_implicit(&self) -> bool {
if let LintConfig::Table(table) = self {
table.priority.is_none()
} else {
true
}
}
}
type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;
#[derive(Deserialize, Debug)]
struct Lints {
#[serde(default)]
rust: LintTable,
#[serde(default)]
clippy: LintTable,
}
#[derive(Deserialize, Debug)]
struct CargoToml {
lints: Lints,
}
#[derive(Default, Debug)]
struct LintsAndGroups {
lints: Vec<Spanned<String>>,
groups: Vec<(Spanned<String>, Spanned<LintConfig>)>,
}
fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
Span::new(
file.start_pos + BytePos::from_usize(range.start),
file.start_pos + BytePos::from_usize(range.end),
SyntaxContext::root(),
None,
)
}
fn check_table(cx: &LateContext<'_>, table: LintTable, groups: &FxHashSet<&str>, file: &SourceFile) {
let mut by_priority = BTreeMap::<_, LintsAndGroups>::new();
for (name, config) in table {
let lints_and_groups = by_priority.entry(config.as_ref().priority()).or_default();
if groups.contains(name.get_ref().as_str()) {
lints_and_groups.groups.push((name, config));
} else {
lints_and_groups.lints.push(name);
}
}
let low_priority = by_priority
.iter()
.find(|(_, lints_and_groups)| !lints_and_groups.lints.is_empty())
.map_or(-1, |(&lowest_lint_priority, _)| lowest_lint_priority - 1);
for (priority, LintsAndGroups { lints, groups }) in by_priority {
let Some(last_lint_alphabetically) = lints.last() else {
continue;
};
for (group, config) in groups {
span_lint_and_then(
cx,
LINT_GROUPS_PRIORITY,
toml_span(group.span(), file),
&format!(
"lint group `{}` has the same priority ({priority}) as a lint",
group.as_ref()
),
|diag| {
let config_span = toml_span(config.span(), file);
if config.as_ref().is_implicit() {
diag.span_label(config_span, "has an implicit priority of 0");
}
// add the label to next lint after this group that has the same priority
let lint = lints
.iter()
.filter(|lint| lint.span().start > group.span().start)
.min_by_key(|lint| lint.span().start)
.unwrap_or(last_lint_alphabetically);
diag.span_label(toml_span(lint.span(), file), "has the same priority as this lint");
diag.note("the order of the lints in the table is ignored by Cargo");
let mut suggestion = String::new();
Serialize::serialize(
&LintConfigTable {
level: config.as_ref().level().into(),
priority: Some(low_priority),
},
toml::ser::ValueSerializer::new(&mut suggestion),
)
.unwrap();
diag.span_suggestion_verbose(
config_span,
format!(
"to have lints override the group set `{}` to a lower priority",
group.as_ref()
),
suggestion,
Applicability::MaybeIncorrect,
);
},
);
}
}
}
pub fn check(cx: &LateContext<'_>) {
if let Ok(file) = cx.tcx.sess.source_map().load_file(Path::new("Cargo.toml"))
&& let Some(src) = file.src.as_deref()
&& let Ok(cargo_toml) = toml::from_str::<CargoToml>(src)
{
let mut rustc_groups = FxHashSet::default();
let mut clippy_groups = FxHashSet::default();
for (group, ..) in unerased_lint_store(cx.tcx.sess).get_lint_groups() {
match group.split_once("::") {
None => {
rustc_groups.insert(group);
},
Some(("clippy", group)) => {
clippy_groups.insert(group);
},
_ => {},
}
}
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
}
}

View file

@ -1,5 +1,6 @@
mod common_metadata; mod common_metadata;
mod feature_name; mod feature_name;
mod lint_groups_priority;
mod multiple_crate_versions; mod multiple_crate_versions;
mod wildcard_dependencies; mod wildcard_dependencies;
@ -165,6 +166,43 @@ declare_clippy_lint! {
"wildcard dependencies being used" "wildcard dependencies being used"
} }
declare_clippy_lint! {
/// ### What it does
/// Checks for lint groups with the same priority as lints in the `Cargo.toml`
/// [`[lints]` table](https://doc.rust-lang.org/cargo/reference/manifest.html#the-lints-section).
///
/// This lint will be removed once [cargo#12918](https://github.com/rust-lang/cargo/issues/12918)
/// is resolved.
///
/// ### Why is this bad?
/// The order of lints in the `[lints]` is ignored, to have a lint override a group the
/// `priority` field needs to be used, otherwise the sort order is undefined.
///
/// ### Known problems
/// Does not check lints inherited using `lints.workspace = true`
///
/// ### Example
/// ```toml
/// # Passed as `--allow=clippy::similar_names --warn=clippy::pedantic`
/// # which results in `similar_names` being `warn`
/// [lints.clippy]
/// pedantic = "warn"
/// similar_names = "allow"
/// ```
/// Use instead:
/// ```toml
/// # Passed as `--warn=clippy::pedantic --allow=clippy::similar_names`
/// # which results in `similar_names` being `allow`
/// [lints.clippy]
/// pedantic = { level = "warn", priority = -1 }
/// similar_names = "allow"
/// ```
#[clippy::version = "1.76.0"]
pub LINT_GROUPS_PRIORITY,
correctness,
"a lint group in `Cargo.toml` at the same priority as a lint"
}
pub struct Cargo { pub struct Cargo {
pub allowed_duplicate_crates: FxHashSet<String>, pub allowed_duplicate_crates: FxHashSet<String>,
pub ignore_publish: bool, pub ignore_publish: bool,
@ -175,7 +213,8 @@ impl_lint_pass!(Cargo => [
REDUNDANT_FEATURE_NAMES, REDUNDANT_FEATURE_NAMES,
NEGATIVE_FEATURE_NAMES, NEGATIVE_FEATURE_NAMES,
MULTIPLE_CRATE_VERSIONS, MULTIPLE_CRATE_VERSIONS,
WILDCARD_DEPENDENCIES WILDCARD_DEPENDENCIES,
LINT_GROUPS_PRIORITY,
]); ]);
impl LateLintPass<'_> for Cargo { impl LateLintPass<'_> for Cargo {
@ -188,6 +227,8 @@ impl LateLintPass<'_> for Cargo {
]; ];
static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS]; static WITH_DEPS_LINTS: &[&Lint] = &[MULTIPLE_CRATE_VERSIONS];
lint_groups_priority::check(cx);
if !NO_DEPS_LINTS if !NO_DEPS_LINTS
.iter() .iter()
.all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID)) .all(|&lint| is_lint_allowed(cx, lint, CRATE_HIR_ID))

View file

@ -18,6 +18,7 @@ mod fn_to_numeric_cast_any;
mod fn_to_numeric_cast_with_truncation; mod fn_to_numeric_cast_with_truncation;
mod ptr_as_ptr; mod ptr_as_ptr;
mod ptr_cast_constness; mod ptr_cast_constness;
mod ref_as_ptr;
mod unnecessary_cast; mod unnecessary_cast;
mod utils; mod utils;
mod zero_ptr; mod zero_ptr;
@ -689,6 +690,30 @@ declare_clippy_lint! {
"using `0 as *{const, mut} T`" "using `0 as *{const, mut} T`"
} }
declare_clippy_lint! {
/// ### What it does
/// Checks for casts of references to pointer using `as`
/// and suggests `std::ptr::from_ref` and `std::ptr::from_mut` instead.
///
/// ### Why is this bad?
/// Using `as` casts may result in silently changing mutability or type.
///
/// ### Example
/// ```no_run
/// let a_ref = &1;
/// let a_ptr = a_ref as *const _;
/// ```
/// Use instead:
/// ```no_run
/// let a_ref = &1;
/// let a_ptr = std::ptr::from_ref(a_ref);
/// ```
#[clippy::version = "1.77.0"]
pub REF_AS_PTR,
pedantic,
"using `as` to cast a reference to pointer"
}
pub struct Casts { pub struct Casts {
msrv: Msrv, msrv: Msrv,
} }
@ -724,6 +749,7 @@ impl_lint_pass!(Casts => [
AS_PTR_CAST_MUT, AS_PTR_CAST_MUT,
CAST_NAN_TO_INT, CAST_NAN_TO_INT,
ZERO_PTR, ZERO_PTR,
REF_AS_PTR,
]); ]);
impl<'tcx> LateLintPass<'tcx> for Casts { impl<'tcx> LateLintPass<'tcx> for Casts {
@ -771,7 +797,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
as_underscore::check(cx, expr, cast_to_hir); as_underscore::check(cx, expr, cast_to_hir);
if self.msrv.meets(msrvs::BORROW_AS_PTR) { if self.msrv.meets(msrvs::PTR_FROM_REF) {
ref_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
} else if self.msrv.meets(msrvs::BORROW_AS_PTR) {
borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir); borrow_as_ptr::check(cx, expr, cast_expr, cast_to_hir);
} }
} }

View file

@ -0,0 +1,55 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_no_std_crate;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{Expr, Mutability, Ty, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, TypeAndMut};
use super::REF_AS_PTR;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_to_hir_ty: &Ty<'_>) {
let (cast_from, cast_to) = (
cx.typeck_results().expr_ty(cast_expr),
cx.typeck_results().expr_ty(expr),
);
if matches!(cast_from.kind(), ty::Ref(..))
&& let ty::RawPtr(TypeAndMut { mutbl: to_mutbl, .. }) = cast_to.kind()
{
let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" };
let fn_name = match to_mutbl {
Mutability::Not => "from_ref",
Mutability::Mut => "from_mut",
};
let mut app = Applicability::MachineApplicable;
let turbofish = match &cast_to_hir_ty.kind {
TyKind::Infer => String::new(),
TyKind::Ptr(mut_ty) => {
if matches!(mut_ty.ty.kind, TyKind::Infer) {
String::new()
} else {
format!(
"::<{}>",
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
)
}
},
_ => return,
};
let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app);
span_lint_and_sugg(
cx,
REF_AS_PTR,
expr.span,
"reference as raw pointer",
"try",
format!("{core_or_std}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"),
app,
);
}
}

View file

@ -71,6 +71,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO,
crate::box_default::BOX_DEFAULT_INFO, crate::box_default::BOX_DEFAULT_INFO,
crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO,
crate::cargo::LINT_GROUPS_PRIORITY_INFO,
crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO, crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO,
crate::cargo::NEGATIVE_FEATURE_NAMES_INFO, crate::cargo::NEGATIVE_FEATURE_NAMES_INFO,
crate::cargo::REDUNDANT_FEATURE_NAMES_INFO, crate::cargo::REDUNDANT_FEATURE_NAMES_INFO,
@ -96,6 +97,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO, crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO,
crate::casts::PTR_AS_PTR_INFO, crate::casts::PTR_AS_PTR_INFO,
crate::casts::PTR_CAST_CONSTNESS_INFO, crate::casts::PTR_CAST_CONSTNESS_INFO,
crate::casts::REF_AS_PTR_INFO,
crate::casts::UNNECESSARY_CAST_INFO, crate::casts::UNNECESSARY_CAST_INFO,
crate::casts::ZERO_PTR_INFO, crate::casts::ZERO_PTR_INFO,
crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO,
@ -212,6 +214,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO, crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO, crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO, crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO, crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO, crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
crate::indexing_slicing::INDEXING_SLICING_INFO, crate::indexing_slicing::INDEXING_SLICING_INFO,
@ -384,6 +387,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::ITER_SKIP_ZERO_INFO, crate::methods::ITER_SKIP_ZERO_INFO,
crate::methods::ITER_WITH_DRAIN_INFO, crate::methods::ITER_WITH_DRAIN_INFO,
crate::methods::JOIN_ABSOLUTE_PATHS_INFO, crate::methods::JOIN_ABSOLUTE_PATHS_INFO,
crate::methods::MANUAL_C_STR_LITERALS_INFO,
crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FILTER_MAP_INFO,
crate::methods::MANUAL_FIND_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO,
crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_IS_VARIANT_AND_INFO,
@ -452,6 +456,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::UNNECESSARY_JOIN_INFO, crate::methods::UNNECESSARY_JOIN_INFO,
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO,
crate::methods::UNNECESSARY_SORT_BY_INFO, crate::methods::UNNECESSARY_SORT_BY_INFO,
crate::methods::UNNECESSARY_TO_OWNED_INFO, crate::methods::UNNECESSARY_TO_OWNED_INFO,
crate::methods::UNWRAP_OR_DEFAULT_INFO, crate::methods::UNWRAP_OR_DEFAULT_INFO,
@ -656,6 +661,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO, crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO,
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO,
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO, crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO, crate::trait_bounds::TYPE_REPETITION_IN_BOUNDS_INFO,

View file

@ -226,7 +226,7 @@ declare_clippy_lint! {
/// unimplemented!(); /// unimplemented!();
/// } /// }
/// ``` /// ```
#[clippy::version = "1.40.0"] #[clippy::version = "1.76.0"]
pub TEST_ATTR_IN_DOCTEST, pub TEST_ATTR_IN_DOCTEST,
suspicious, suspicious,
"presence of `#[test]` in code examples" "presence of `#[test]` in code examples"

View file

@ -3,15 +3,14 @@ use clippy_utils::higher::VecArgs;
use clippy_utils::source::snippet_opt; use clippy_utils::source::snippet_opt;
use clippy_utils::ty::type_diagnostic_name; use clippy_utils::ty::type_diagnostic_name;
use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::usage::{local_used_after_expr, local_used_in};
use clippy_utils::{higher, is_adjusted, path_to_local, path_to_local_id}; use clippy_utils::{get_path_from_caller_to_method_type, higher, is_adjusted, path_to_local, path_to_local_id};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety}; use rustc_hir::{BindingAnnotation, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, TyKind, Unsafety};
use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{ use rustc_middle::ty::{
self, Binder, ClosureArgs, ClosureKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, GenericArgsRef, self, Binder, ClosureArgs, ClosureKind, FnSig, GenericArg, GenericArgKind, ImplPolarity, List, Region, RegionKind,
ImplPolarity, List, Region, RegionKind, Ty, TypeVisitableExt, TypeckResults, Ty, TypeVisitableExt, TypeckResults,
}; };
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
@ -21,8 +20,8 @@ use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Checks for closures which just call another function where /// Checks for closures which just call another function where
/// the function can be called directly. `unsafe` functions or calls where types /// the function can be called directly. `unsafe` functions, calls where types
/// get adjusted are ignored. /// get adjusted or where the callee is marked `#[track_caller]` are ignored.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Needlessly creating a closure adds code for no benefit /// Needlessly creating a closure adds code for no benefit
@ -136,7 +135,14 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
.map_or(callee_ty, |a| a.target.peel_refs()); .map_or(callee_ty, |a| a.target.peel_refs());
let sig = match callee_ty_adjusted.kind() { let sig = match callee_ty_adjusted.kind() {
ty::FnDef(def, _) => cx.tcx.fn_sig(def).skip_binder().skip_binder(), ty::FnDef(def, _) => {
// Rewriting `x(|| f())` to `x(f)` where f is marked `#[track_caller]` moves the `Location`
if cx.tcx.has_attr(*def, sym::track_caller) {
return;
}
cx.tcx.fn_sig(def).skip_binder().skip_binder()
},
ty::FnPtr(sig) => sig.skip_binder(), ty::FnPtr(sig) => sig.skip_binder(),
ty::Closure(_, subs) => cx ty::Closure(_, subs) => cx
.tcx .tcx
@ -186,6 +192,7 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
}, },
ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => { ExprKind::MethodCall(path, self_, args, _) if check_inputs(typeck, body.params, Some(self_), args) => {
if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id) if let Some(method_def_id) = typeck.type_dependent_def_id(body.value.hir_id)
&& !cx.tcx.has_attr(method_def_id, sym::track_caller)
&& check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) && check_sig(cx, closure, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder())
{ {
span_lint_and_then( span_lint_and_then(
@ -195,11 +202,12 @@ impl<'tcx> LateLintPass<'tcx> for EtaReduction {
"redundant closure", "redundant closure",
|diag| { |diag| {
let args = typeck.node_args(body.value.hir_id); let args = typeck.node_args(body.value.hir_id);
let name = get_ufcs_type_name(cx, method_def_id, args); let caller = self_.hir_id.owner.def_id;
let type_name = get_path_from_caller_to_method_type(cx.tcx, caller, method_def_id, args);
diag.span_suggestion( diag.span_suggestion(
expr.span, expr.span,
"replace the closure with the method itself", "replace the closure with the method itself",
format!("{}::{}", name, path.ident.name), format!("{}::{}", type_name, path.ident.name),
Applicability::MachineApplicable, Applicability::MachineApplicable,
); );
}, },
@ -301,27 +309,3 @@ fn has_late_bound_to_non_late_bound_regions(from_sig: FnSig<'_>, to_sig: FnSig<'
.zip(to_sig.inputs_and_output) .zip(to_sig.inputs_and_output)
.any(|(from_ty, to_ty)| check_ty(from_ty, to_ty)) .any(|(from_ty, to_ty)| check_ty(from_ty, to_ty))
} }
fn get_ufcs_type_name<'tcx>(cx: &LateContext<'tcx>, method_def_id: DefId, args: GenericArgsRef<'tcx>) -> String {
let assoc_item = cx.tcx.associated_item(method_def_id);
let def_id = assoc_item.container_id(cx.tcx);
match assoc_item.container {
ty::TraitContainer => cx.tcx.def_path_str(def_id),
ty::ImplContainer => {
let ty = cx.tcx.type_of(def_id).instantiate_identity();
match ty.kind() {
ty::Adt(adt, _) => cx.tcx.def_path_str(adt.did()),
ty::Array(..)
| ty::Dynamic(..)
| ty::Never
| ty::RawPtr(_)
| ty::Ref(..)
| ty::Slice(_)
| ty::Tuple(_) => {
format!("<{}>", EarlyBinder::bind(ty).instantiate(cx.tcx, args))
},
_ => ty.to_string(),
}
},
}
}

View file

@ -0,0 +1,133 @@
use clippy_config::msrvs::Msrv;
use clippy_utils::diagnostics::span_lint;
use rustc_attr::{StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_semver::RustcVersion;
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
///
/// This lint checks that no function newer than the defined MSRV (minimum
/// supported rust version) is used in the crate.
///
/// ### Why is this bad?
///
/// It would prevent the crate to be actually used with the specified MSRV.
///
/// ### Example
/// ```no_run
/// // MSRV of 1.3.0
/// use std::thread::sleep;
/// use std::time::Duration;
///
/// // Sleep was defined in `1.4.0`.
/// sleep(Duration::new(1, 0));
/// ```
///
/// To fix this problem, either increase your MSRV or use another item
/// available in your current MSRV.
#[clippy::version = "1.77.0"]
pub INCOMPATIBLE_MSRV,
suspicious,
"ensures that all items used in the crate are available for the current MSRV"
}
pub struct IncompatibleMsrv {
msrv: Msrv,
is_above_msrv: FxHashMap<DefId, RustcVersion>,
}
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
impl IncompatibleMsrv {
pub fn new(msrv: Msrv) -> Self {
Self {
msrv,
is_above_msrv: FxHashMap::default(),
}
}
#[allow(clippy::cast_lossless)]
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
if let Some(version) = self.is_above_msrv.get(&def_id) {
return *version;
}
let version = if let Some(version) = tcx
.lookup_stability(def_id)
.and_then(|stability| match stability.level {
StabilityLevel::Stable {
since: StableSince::Version(version),
..
} => Some(RustcVersion::new(
version.major as _,
version.minor as _,
version.patch as _,
)),
_ => None,
}) {
version
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
self.get_def_id_version(tcx, parent_def_id)
} else {
RustcVersion::new(1, 0, 0)
};
self.is_above_msrv.insert(def_id, version);
version
}
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
if def_id.is_local() {
// We don't check local items since their MSRV is supposed to always be valid.
return;
}
let version = self.get_def_id_version(cx.tcx, def_id);
if self.msrv.meets(version) {
return;
}
self.emit_lint_for(cx, span, version);
}
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
span_lint(
cx,
INCOMPATIBLE_MSRV,
span,
&format!(
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
self.msrv
),
);
}
}
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
extract_msrv_attr!(LateContext);
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if self.msrv.current().is_none() {
// If there is no MSRV, then no need to check anything...
return;
}
match expr.kind {
ExprKind::MethodCall(_, _, _, span) => {
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
self.emit_lint_if_under_msrv(cx, method_did, span);
}
},
ExprKind::Call(call, [_]) => {
if let ExprKind::Path(qpath) = call.kind
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
{
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
}
},
_ => {},
}
}
}

View file

@ -34,7 +34,7 @@ declare_clippy_lint! {
/// let value = &my_map[key]; /// let value = &my_map[key];
/// } /// }
/// ``` /// ```
#[clippy::version = "1.75.0"] #[clippy::version = "1.76.0"]
pub ITER_OVER_HASH_TYPE, pub ITER_OVER_HASH_TYPE,
restriction, restriction,
"iterating over unordered hash-based types (`HashMap` and `HashSet`)" "iterating over unordered hash-based types (`HashMap` and `HashSet`)"

View file

@ -26,6 +26,7 @@ extern crate rustc_abi;
extern crate rustc_arena; extern crate rustc_arena;
extern crate rustc_ast; extern crate rustc_ast;
extern crate rustc_ast_pretty; extern crate rustc_ast_pretty;
extern crate rustc_attr;
extern crate rustc_data_structures; extern crate rustc_data_structures;
extern crate rustc_driver; extern crate rustc_driver;
extern crate rustc_errors; extern crate rustc_errors;
@ -153,6 +154,7 @@ mod implicit_return;
mod implicit_saturating_add; mod implicit_saturating_add;
mod implicit_saturating_sub; mod implicit_saturating_sub;
mod implied_bounds_in_impls; mod implied_bounds_in_impls;
mod incompatible_msrv;
mod inconsistent_struct_constructor; mod inconsistent_struct_constructor;
mod index_refutable_slice; mod index_refutable_slice;
mod indexing_slicing; mod indexing_slicing;
@ -325,6 +327,7 @@ mod temporary_assignment;
mod tests_outside_test_module; mod tests_outside_test_module;
mod thread_local_initializer_can_be_made_const; mod thread_local_initializer_can_be_made_const;
mod to_digit_is_some; mod to_digit_is_some;
mod to_string_trait_impl;
mod trailing_empty_array; mod trailing_empty_array;
mod trait_bounds; mod trait_bounds;
mod transmute; mod transmute;
@ -521,6 +524,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
ref allowed_dotfiles, ref allowed_dotfiles,
ref allowed_idents_below_min_chars, ref allowed_idents_below_min_chars,
ref allowed_scripts, ref allowed_scripts,
ref allowed_wildcard_imports,
ref arithmetic_side_effects_allowed_binary, ref arithmetic_side_effects_allowed_binary,
ref arithmetic_side_effects_allowed_unary, ref arithmetic_side_effects_allowed_unary,
ref arithmetic_side_effects_allowed, ref arithmetic_side_effects_allowed,
@ -575,6 +579,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
check_private_items, check_private_items,
pub_underscore_fields_behavior, pub_underscore_fields_behavior,
ref allowed_duplicate_crates, ref allowed_duplicate_crates,
allow_comparison_to_zero,
blacklisted_names: _, blacklisted_names: _,
cyclomatic_complexity_threshold: _, cyclomatic_complexity_threshold: _,
@ -872,7 +877,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
)) ))
}); });
store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap));
store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(warn_on_all_wildcard_imports))); store.register_late_pass(move |_| {
Box::new(wildcard_imports::WildcardImports::new(
warn_on_all_wildcard_imports,
allowed_wildcard_imports.clone(),
))
});
store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default()); store.register_late_pass(|_| Box::<redundant_pub_crate::RedundantPubCrate>::default());
store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress)); store.register_late_pass(|_| Box::new(unnamed_address::UnnamedAddress));
store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default()); store.register_late_pass(|_| Box::<dereference::Dereferencing<'_>>::default());
@ -968,7 +978,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty));
store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv())));
store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv())));
store.register_late_pass(move |_| Box::new(operators::Operators::new(verbose_bit_mask_threshold))); store.register_late_pass(move |_| {
Box::new(operators::Operators::new(
verbose_bit_mask_threshold,
allow_comparison_to_zero,
))
});
store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default()); store.register_late_pass(|_| Box::<std_instead_of_core::StdReexports>::default());
store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv()))); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(msrv())));
store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone));
@ -1094,6 +1109,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv())) Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
}); });
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl));
// add lints here, do not remove this comment, it's used in `new_lint` // add lints here, do not remove this comment, it's used in `new_lint`
} }

View file

@ -672,7 +672,7 @@ declare_clippy_lint! {
/// } /// }
/// } /// }
/// ``` /// ```
#[clippy::version = "1.75.0"] #[clippy::version = "1.76.0"]
pub INFINITE_LOOP, pub INFINITE_LOOP,
restriction, restriction,
"possibly unintended infinite loop" "possibly unintended infinite loop"

View file

@ -201,12 +201,12 @@ fn never_loop_expr<'tcx>(
}) })
}) })
}, },
ExprKind::Block(b, l) => { ExprKind::Block(b, _) => {
if l.is_some() { if b.targeted_by_break {
local_labels.push((b.hir_id, false)); local_labels.push((b.hir_id, false));
} }
let ret = never_loop_block(cx, b, local_labels, main_loop_id); let ret = never_loop_block(cx, b, local_labels, main_loop_id);
let jumped_to = l.is_some() && local_labels.pop().unwrap().1; let jumped_to = b.targeted_by_break && local_labels.pop().unwrap().1;
match ret { match ret {
NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal, NeverLoopResult::Diverging if jumped_to => NeverLoopResult::Normal,
_ => ret, _ => ret,

View file

@ -11,6 +11,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
use rustc_session::impl_lint_pass; use rustc_session::impl_lint_pass;
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
use rustc_span::Span;
const ACCEPTABLE_METHODS: [&[&str]; 5] = [ const ACCEPTABLE_METHODS: [&[&str]; 5] = [
&paths::BINARYHEAP_ITER, &paths::BINARYHEAP_ITER,
@ -28,6 +29,7 @@ const ACCEPTABLE_TYPES: [(rustc_span::Symbol, Option<RustcVersion>); 7] = [
(sym::Vec, None), (sym::Vec, None),
(sym::VecDeque, None), (sym::VecDeque, None),
]; ];
const MAP_TYPES: [rustc_span::Symbol; 2] = [sym::BTreeMap, sym::HashMap];
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -44,6 +46,7 @@ declare_clippy_lint! {
/// ```no_run /// ```no_run
/// let mut vec = vec![0, 1, 2]; /// let mut vec = vec![0, 1, 2];
/// vec.retain(|x| x % 2 == 0); /// vec.retain(|x| x % 2 == 0);
/// vec.retain(|x| x % 2 == 0);
/// ``` /// ```
#[clippy::version = "1.64.0"] #[clippy::version = "1.64.0"]
pub MANUAL_RETAIN, pub MANUAL_RETAIN,
@ -74,9 +77,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
&& let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id) && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id)
&& cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id) && cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id)
{ {
check_into_iter(cx, parent_expr, left_expr, target_expr, &self.msrv); check_into_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_iter(cx, parent_expr, left_expr, target_expr, &self.msrv); check_iter(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
check_to_owned(cx, parent_expr, left_expr, target_expr, &self.msrv); check_to_owned(cx, left_expr, target_expr, parent_expr.span, &self.msrv);
} }
} }
@ -85,9 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for ManualRetain {
fn check_into_iter( fn check_into_iter(
cx: &LateContext<'_>, cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>, target_expr: &hir::Expr<'_>,
parent_expr_span: Span,
msrv: &Msrv, msrv: &Msrv,
) { ) {
if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind if let hir::ExprKind::MethodCall(_, into_iter_expr, [_], _) = &target_expr.kind
@ -98,16 +101,39 @@ fn check_into_iter(
&& Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn() && Some(into_iter_def_id) == cx.tcx.lang_items().into_iter_fn()
&& match_acceptable_type(cx, left_expr, msrv) && match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = target_expr.kind
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
&& let filter_body = cx.tcx.hir().body(closure.body)
&& let [filter_params] = filter_body.params
{ {
suggest(cx, parent_expr, left_expr, target_expr); if match_map_type(cx, left_expr) {
if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind {
if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) {
make_span_lint_and_sugg(cx, parent_expr_span, sugg);
}
}
// Cannot lint other cases because `retain` requires two parameters
} else {
// Can always move because `retain` and `filter` have the same bound on the predicate
// for other types
make_span_lint_and_sugg(
cx,
parent_expr_span,
format!(
"{}.retain({})",
snippet(cx, left_expr.span, ".."),
snippet(cx, closure_expr.span, "..")
),
);
}
} }
} }
fn check_iter( fn check_iter(
cx: &LateContext<'_>, cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>, target_expr: &hir::Expr<'_>,
parent_expr_span: Span,
msrv: &Msrv, msrv: &Msrv,
) { ) {
if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind
@ -122,16 +148,50 @@ fn check_iter(
&& match_acceptable_def_path(cx, iter_expr_def_id) && match_acceptable_def_path(cx, iter_expr_def_id)
&& match_acceptable_type(cx, left_expr, msrv) && match_acceptable_type(cx, left_expr, msrv)
&& SpanlessEq::new(cx).eq_expr(left_expr, struct_expr) && SpanlessEq::new(cx).eq_expr(left_expr, struct_expr)
&& let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
&& let hir::ExprKind::Closure(closure) = closure_expr.kind
&& let filter_body = cx.tcx.hir().body(closure.body)
&& let [filter_params] = filter_body.params
{ {
suggest(cx, parent_expr, left_expr, filter_expr); match filter_params.pat.kind {
// hir::PatKind::Binding(_, _, _, None) => {
// // Be conservative now. Do nothing here.
// // TODO: Ideally, we can rewrite the lambda by stripping one level of reference
// },
hir::PatKind::Tuple([_, _], _) => {
// the `&&` reference for the `filter` method will be auto derefed to `ref`
// so, we can directly use the lambda
// https://doc.rust-lang.org/reference/patterns.html#binding-modes
make_span_lint_and_sugg(
cx,
parent_expr_span,
format!(
"{}.retain({})",
snippet(cx, left_expr.span, ".."),
snippet(cx, closure_expr.span, "..")
),
);
},
hir::PatKind::Ref(pat, _) => make_span_lint_and_sugg(
cx,
parent_expr_span,
format!(
"{}.retain(|{}| {})",
snippet(cx, left_expr.span, ".."),
snippet(cx, pat.span, ".."),
snippet(cx, filter_body.value.span, "..")
),
),
_ => {},
}
} }
} }
fn check_to_owned( fn check_to_owned(
cx: &LateContext<'_>, cx: &LateContext<'_>,
parent_expr: &hir::Expr<'_>,
left_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>,
target_expr: &hir::Expr<'_>, target_expr: &hir::Expr<'_>,
parent_expr_span: Span,
msrv: &Msrv, msrv: &Msrv,
) { ) {
if msrv.meets(msrvs::STRING_RETAIN) if msrv.meets(msrvs::STRING_RETAIN)
@ -147,43 +207,25 @@ fn check_to_owned(
&& let ty = cx.typeck_results().expr_ty(str_expr).peel_refs() && let ty = cx.typeck_results().expr_ty(str_expr).peel_refs()
&& is_type_lang_item(cx, ty, hir::LangItem::String) && is_type_lang_item(cx, ty, hir::LangItem::String)
&& SpanlessEq::new(cx).eq_expr(left_expr, str_expr) && SpanlessEq::new(cx).eq_expr(left_expr, str_expr)
{ && let hir::ExprKind::MethodCall(_, _, [closure_expr], _) = filter_expr.kind
suggest(cx, parent_expr, left_expr, filter_expr); && let hir::ExprKind::Closure(closure) = closure_expr.kind
} && let filter_body = cx.tcx.hir().body(closure.body)
}
fn suggest(cx: &LateContext<'_>, parent_expr: &hir::Expr<'_>, left_expr: &hir::Expr<'_>, filter_expr: &hir::Expr<'_>) {
if let hir::ExprKind::MethodCall(_, _, [closure], _) = filter_expr.kind
&& let hir::ExprKind::Closure(&hir::Closure { body, .. }) = closure.kind
&& let filter_body = cx.tcx.hir().body(body)
&& let [filter_params] = filter_body.params && let [filter_params] = filter_body.params
&& let Some(sugg) = match filter_params.pat.kind {
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
"{}.retain(|{filter_param_ident}| {})",
snippet(cx, left_expr.span, ".."),
snippet(cx, filter_body.value.span, "..")
)),
hir::PatKind::Tuple([key_pat, value_pat], _) => make_sugg(cx, key_pat, value_pat, left_expr, filter_body),
hir::PatKind::Ref(pat, _) => match pat.kind {
hir::PatKind::Binding(_, _, filter_param_ident, None) => Some(format!(
"{}.retain(|{filter_param_ident}| {})",
snippet(cx, left_expr.span, ".."),
snippet(cx, filter_body.value.span, "..")
)),
_ => None,
},
_ => None,
}
{ {
span_lint_and_sugg( if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind {
cx, make_span_lint_and_sugg(
MANUAL_RETAIN, cx,
parent_expr.span, parent_expr_span,
"this expression can be written more simply using `.retain()`", format!(
"consider calling `.retain()` instead", "{}.retain(|{}| {})",
sugg, snippet(cx, left_expr.span, ".."),
Applicability::MachineApplicable, snippet(cx, pat.span, ".."),
); snippet(cx, filter_body.value.span, "..")
),
);
}
// Be conservative now. Do nothing for the `Binding` case.
// TODO: Ideally, we can rewrite the lambda by stripping one level of reference
} }
} }
@ -229,3 +271,20 @@ fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: &Msrv
&& acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv)) && acceptable_msrv.map_or(true, |acceptable_msrv| msrv.meets(acceptable_msrv))
}) })
} }
fn match_map_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
MAP_TYPES.iter().any(|ty| is_type_diagnostic_item(cx, expr_ty, *ty))
}
fn make_span_lint_and_sugg(cx: &LateContext<'_>, span: Span, sugg: String) {
span_lint_and_sugg(
cx,
MANUAL_RETAIN,
span,
"this expression can be written more simply using `.retain()`",
"consider calling `.retain()` instead",
sugg,
Applicability::MachineApplicable,
);
}

View file

@ -0,0 +1,197 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet;
use rustc_ast::{LitKind, StrStyle};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Node, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::{sym, Span, Symbol};
use super::MANUAL_C_STR_LITERALS;
/// Checks:
/// - `b"...".as_ptr()`
/// - `b"...".as_ptr().cast()`
/// - `"...".as_ptr()`
/// - `"...".as_ptr().cast()`
///
/// Iff the parent call of `.cast()` isn't `CStr::from_ptr`, to avoid linting twice.
pub(super) fn check_as_ptr<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
receiver: &'tcx Expr<'tcx>,
msrv: &Msrv,
) {
if let ExprKind::Lit(lit) = receiver.kind
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node
&& let casts_removed = peel_ptr_cast_ancestors(cx, expr)
&& !get_parent_expr(cx, casts_removed).is_some_and(
|parent| matches!(parent.kind, ExprKind::Call(func, _) if is_c_str_function(cx, func).is_some()),
)
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
&& msrv.meets(msrvs::C_STR_LITERALS)
{
span_lint_and_sugg(
cx,
MANUAL_C_STR_LITERALS,
receiver.span,
"manually constructing a nul-terminated string",
r#"use a `c""` literal"#,
sugg,
// an additional cast may be needed, since the type of `CStr::as_ptr` and
// `"".as_ptr()` can differ and is platform dependent
Applicability::HasPlaceholders,
);
}
}
/// Checks if the callee is a "relevant" `CStr` function considered by this lint.
/// Returns the function name.
fn is_c_str_function(cx: &LateContext<'_>, func: &Expr<'_>) -> Option<Symbol> {
if let ExprKind::Path(QPath::TypeRelative(cstr, fn_name)) = &func.kind
&& let TyKind::Path(QPath::Resolved(_, ty_path)) = &cstr.kind
&& cx.tcx.lang_items().c_str() == ty_path.res.opt_def_id()
{
Some(fn_name.ident.name)
} else {
None
}
}
/// Checks calls to the `CStr` constructor functions:
/// - `CStr::from_bytes_with_nul(..)`
/// - `CStr::from_bytes_with_nul_unchecked(..)`
/// - `CStr::from_ptr(..)`
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>], msrv: &Msrv) {
if let Some(fn_name) = is_c_str_function(cx, func)
&& let [arg] = args
&& msrv.meets(msrvs::C_STR_LITERALS)
{
match fn_name.as_str() {
name @ ("from_bytes_with_nul" | "from_bytes_with_nul_unchecked")
if !arg.span.from_expansion()
&& let ExprKind::Lit(lit) = arg.kind
&& let LitKind::ByteStr(_, StrStyle::Cooked) | LitKind::Str(_, StrStyle::Cooked) = lit.node =>
{
check_from_bytes(cx, expr, arg, name);
},
"from_ptr" => check_from_ptr(cx, expr, arg),
_ => {},
}
}
}
/// Checks `CStr::from_ptr(b"foo\0".as_ptr().cast())`
fn check_from_ptr(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) {
if let ExprKind::MethodCall(method, lit, ..) = peel_ptr_cast(arg).kind
&& method.ident.name == sym::as_ptr
&& !lit.span.from_expansion()
&& let ExprKind::Lit(lit) = lit.kind
&& let LitKind::ByteStr(_, StrStyle::Cooked) = lit.node
&& let Some(sugg) = rewrite_as_cstr(cx, lit.span)
{
span_lint_and_sugg(
cx,
MANUAL_C_STR_LITERALS,
expr.span,
"calling `CStr::from_ptr` with a byte string literal",
r#"use a `c""` literal"#,
sugg,
Applicability::MachineApplicable,
);
}
}
/// Checks `CStr::from_bytes_with_nul(b"foo\0")`
fn check_from_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, method: &str) {
let (span, applicability) = if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(method, ..) = parent.kind
&& [sym::unwrap, sym::expect].contains(&method.ident.name)
{
(parent.span, Applicability::MachineApplicable)
} else if method == "from_bytes_with_nul_unchecked" {
// `*_unchecked` returns `&CStr` directly, nothing needs to be changed
(expr.span, Applicability::MachineApplicable)
} else {
// User needs to remove error handling, can't be machine applicable
(expr.span, Applicability::HasPlaceholders)
};
let Some(sugg) = rewrite_as_cstr(cx, arg.span) else {
return;
};
span_lint_and_sugg(
cx,
MANUAL_C_STR_LITERALS,
span,
"calling `CStr::new` with a byte string literal",
r#"use a `c""` literal"#,
sugg,
applicability,
);
}
/// Rewrites a byte string literal to a c-str literal.
/// `b"foo\0"` -> `c"foo"`
///
/// Returns `None` if it doesn't end in a NUL byte.
fn rewrite_as_cstr(cx: &LateContext<'_>, span: Span) -> Option<String> {
let mut sugg = String::from("c") + snippet(cx, span.source_callsite(), "..").trim_start_matches('b');
// NUL byte should always be right before the closing quote.
if let Some(quote_pos) = sugg.rfind('"') {
// Possible values right before the quote:
// - literal NUL value
if sugg.as_bytes()[quote_pos - 1] == b'\0' {
sugg.remove(quote_pos - 1);
}
// - \x00
else if sugg[..quote_pos].ends_with("\\x00") {
sugg.replace_range(quote_pos - 4..quote_pos, "");
}
// - \0
else if sugg[..quote_pos].ends_with("\\0") {
sugg.replace_range(quote_pos - 2..quote_pos, "");
}
// No known suffix, so assume it's not a C-string.
else {
return None;
}
}
Some(sugg)
}
fn get_cast_target<'tcx>(e: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
match &e.kind {
ExprKind::MethodCall(method, receiver, [], _) if method.ident.as_str() == "cast" => Some(receiver),
ExprKind::Cast(expr, _) => Some(expr),
_ => None,
}
}
/// `x.cast()` -> `x`
/// `x as *const _` -> `x`
/// `x` -> `x` (returns the same expression for non-cast exprs)
fn peel_ptr_cast<'tcx>(e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
get_cast_target(e).map_or(e, peel_ptr_cast)
}
/// Same as `peel_ptr_cast`, but the other way around, by walking up the ancestor cast expressions:
///
/// `foo(x.cast() as *const _)`
/// ^ given this `x` expression, returns the `foo(...)` expression
fn peel_ptr_cast_ancestors<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> {
let mut prev = e;
for (_, node) in cx.tcx.hir().parent_iter(e.hir_id) {
if let Node::Expr(e) = node
&& get_cast_target(e).is_some()
{
prev = e;
} else {
break;
}
}
prev
}

View file

@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
unwrap_arg: &'tcx hir::Expr<'_>, unwrap_arg: &'tcx hir::Expr<'_>,
msrv: &Msrv, msrv: &Msrv,
) -> bool { ) -> bool {
// lint if the caller of `map()` is an `Option` // lint if the caller of `map()` is an `Option` or a `Result`.
let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option); let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option);
let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);

View file

@ -51,6 +51,7 @@ mod iter_skip_zero;
mod iter_with_drain; mod iter_with_drain;
mod iterator_step_by_zero; mod iterator_step_by_zero;
mod join_absolute_paths; mod join_absolute_paths;
mod manual_c_str_literals;
mod manual_is_variant_and; mod manual_is_variant_and;
mod manual_next_back; mod manual_next_back;
mod manual_ok_or; mod manual_ok_or;
@ -113,6 +114,7 @@ mod unnecessary_iter_cloned;
mod unnecessary_join; mod unnecessary_join;
mod unnecessary_lazy_eval; mod unnecessary_lazy_eval;
mod unnecessary_literal_unwrap; mod unnecessary_literal_unwrap;
mod unnecessary_result_map_or_else;
mod unnecessary_sort_by; mod unnecessary_sort_by;
mod unnecessary_to_owned; mod unnecessary_to_owned;
mod unwrap_expect_used; mod unwrap_expect_used;
@ -3951,6 +3953,64 @@ declare_clippy_lint! {
"cloning an `Option` via `as_ref().cloned()`" "cloning an `Option` via `as_ref().cloned()`"
} }
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `.map_or_else()` "map closure" for `Result` type.
///
/// ### Why is this bad?
/// This can be written more concisely by using `unwrap_or_else()`.
///
/// ### Example
/// ```no_run
/// # fn handle_error(_: ()) -> u32 { 0 }
/// let x: Result<u32, ()> = Ok(0);
/// let y = x.map_or_else(|err| handle_error(err), |n| n);
/// ```
/// Use instead:
/// ```no_run
/// # fn handle_error(_: ()) -> u32 { 0 }
/// let x: Result<u32, ()> = Ok(0);
/// let y = x.unwrap_or_else(|err| handle_error(err));
/// ```
#[clippy::version = "1.77.0"]
pub UNNECESSARY_RESULT_MAP_OR_ELSE,
suspicious,
"making no use of the \"map closure\" when calling `.map_or_else(|err| handle_error(err), |n| n)`"
}
declare_clippy_lint! {
/// Checks for the manual creation of C strings (a string with a `NUL` byte at the end), either
/// through one of the `CStr` constructor functions, or more plainly by calling `.as_ptr()`
/// on a (byte) string literal with a hardcoded `\0` byte at the end.
///
/// ### Why is this bad?
/// This can be written more concisely using `c"str"` literals and is also less error-prone,
/// because the compiler checks for interior `NUL` bytes and the terminating `NUL` byte is inserted automatically.
///
/// ### Example
/// ```no_run
/// # use std::ffi::CStr;
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
/// fn needs_cstr(_: &CStr) {}
///
/// needs_cstr(CStr::from_bytes_with_nul(b"Hello\0").unwrap());
/// unsafe { libc::puts("World\0".as_ptr().cast()) }
/// ```
/// Use instead:
/// ```no_run
/// # use std::ffi::CStr;
/// # mod libc { pub unsafe fn puts(_: *const i8) {} }
/// fn needs_cstr(_: &CStr) {}
///
/// needs_cstr(c"Hello");
/// unsafe { libc::puts(c"World".as_ptr()) }
/// ```
#[clippy::version = "1.76.0"]
pub MANUAL_C_STR_LITERALS,
pedantic,
r#"creating a `CStr` through functions when `c""` literals can be used"#
}
pub struct Methods { pub struct Methods {
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
msrv: Msrv, msrv: Msrv,
@ -4109,6 +4169,8 @@ impl_lint_pass!(Methods => [
MANUAL_IS_VARIANT_AND, MANUAL_IS_VARIANT_AND,
STR_SPLIT_AT_NEWLINE, STR_SPLIT_AT_NEWLINE,
OPTION_AS_REF_CLONED, OPTION_AS_REF_CLONED,
UNNECESSARY_RESULT_MAP_OR_ELSE,
MANUAL_C_STR_LITERALS,
]); ]);
/// Extracts a method call name, args, and `Span` of the method name. /// Extracts a method call name, args, and `Span` of the method name.
@ -4136,6 +4198,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
hir::ExprKind::Call(func, args) => { hir::ExprKind::Call(func, args) => {
from_iter_instead_of_collect::check(cx, expr, args, func); from_iter_instead_of_collect::check(cx, expr, args, func);
unnecessary_fallible_conversions::check_function(cx, expr, func); unnecessary_fallible_conversions::check_function(cx, expr, func);
manual_c_str_literals::check(cx, expr, func, args, &self.msrv);
}, },
hir::ExprKind::MethodCall(method_call, receiver, args, _) => { hir::ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span; let method_span = method_call.ident.span;
@ -4354,6 +4417,7 @@ impl Methods {
} }
}, },
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv), ("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv), ("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
("cloned", []) => { ("cloned", []) => {
@ -4592,6 +4656,7 @@ impl Methods {
}, },
("map_or_else", [def, map]) => { ("map_or_else", [def, map]) => {
result_map_or_else_none::check(cx, expr, recv, def, map); result_map_or_else_none::check(cx, expr, recv, def, map);
unnecessary_result_map_or_else::check(cx, expr, recv, def, map);
}, },
("next", []) => { ("next", []) => {
if let Some((name2, recv2, args2, _, _)) = method_call(recv) { if let Some((name2, recv2, args2, _, _)) = method_call(recv) {

View file

@ -99,7 +99,6 @@ fn check_fold_with_op(
cx, cx,
UNNECESSARY_FOLD, UNNECESSARY_FOLD,
fold_span.with_hi(expr.span.hi()), fold_span.with_hi(expr.span.hi()),
// TODO #2371 don't suggest e.g., .any(|x| f(x)) if we can suggest .any(f)
"this `.fold` can be written more succinctly using another method", "this `.fold` can be written more succinctly using another method",
"try", "try",
sugg, sugg,

View file

@ -0,0 +1,95 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::peel_blocks;
use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::{Closure, Expr, ExprKind, HirId, QPath, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use super::UNNECESSARY_RESULT_MAP_OR_ELSE;
fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, def_arg: &Expr<'_>) {
let msg = "unused \"map closure\" when calling `Result::map_or_else` value";
let self_snippet = snippet(cx, recv.span, "..");
let err_snippet = snippet(cx, def_arg.span, "..");
span_lint_and_sugg(
cx,
UNNECESSARY_RESULT_MAP_OR_ELSE,
expr.span,
msg,
"consider using `unwrap_or_else`",
format!("{self_snippet}.unwrap_or_else({err_snippet})"),
Applicability::MachineApplicable,
);
}
fn get_last_chain_binding_hir_id(mut hir_id: HirId, statements: &[Stmt<'_>]) -> Option<HirId> {
for stmt in statements {
if let StmtKind::Local(local) = stmt.kind
&& let Some(init) = local.init
&& let ExprKind::Path(QPath::Resolved(_, path)) = init.kind
&& let hir::def::Res::Local(local_hir_id) = path.res
&& local_hir_id == hir_id
{
hir_id = local.pat.hir_id;
} else {
return None;
}
}
Some(hir_id)
}
fn handle_qpath(
cx: &LateContext<'_>,
expr: &Expr<'_>,
recv: &Expr<'_>,
def_arg: &Expr<'_>,
expected_hir_id: HirId,
qpath: QPath<'_>,
) {
if let QPath::Resolved(_, path) = qpath
&& let hir::def::Res::Local(hir_id) = path.res
&& expected_hir_id == hir_id
{
emit_lint(cx, expr, recv, def_arg);
}
}
/// lint use of `_.map_or_else(|err| err, |n| n)` for `Result`s.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
recv: &'tcx Expr<'_>,
def_arg: &'tcx Expr<'_>,
map_arg: &'tcx Expr<'_>,
) {
// lint if the caller of `map_or_else()` is a `Result`
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result)
&& let ExprKind::Closure(&Closure { body, .. }) = map_arg.kind
&& let body = cx.tcx.hir().body(body)
&& let Some(first_param) = body.params.first()
{
let body_expr = peel_blocks(body.value);
match body_expr.kind {
ExprKind::Path(qpath) => {
handle_qpath(cx, expr, recv, def_arg, first_param.pat.hir_id, qpath);
},
// If this is a block (that wasn't peeled off), then it means there are statements.
ExprKind::Block(block, _) => {
if let Some(block_expr) = block.expr
// First we ensure that this is a "binding chain" (each statement is a binding
// of the previous one) and that it is a binding of the closure argument.
&& let Some(last_chain_binding_id) =
get_last_chain_binding_hir_id(first_param.pat.hir_id, block.stmts)
&& let ExprKind::Path(qpath) = block_expr.kind
{
handle_qpath(cx, expr, recv, def_arg, last_chain_binding_id, qpath);
}
},
_ => {},
}
}
}

View file

@ -357,7 +357,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
} }
}, },
ExprKind::Block(block, _) => { ExprKind::Block(block, _) => {
if block.stmts.is_empty() { if block.stmts.is_empty() && !block.targeted_by_break {
block.expr.as_ref().and_then(|e| { block.expr.as_ref().and_then(|e| {
match block.rules { match block.rules {
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None, BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) => None,

View file

@ -252,7 +252,7 @@ impl<'tcx> LateLintPass<'tcx> for OnlyUsedInRecursion {
{ {
( (
trait_item_id, trait_item_id,
FnKind::ImplTraitFn(cx.tcx.erase_regions(trait_ref.args) as *const _ as usize), FnKind::ImplTraitFn(std::ptr::from_ref(cx.tcx.erase_regions(trait_ref.args)) as usize),
usize::from(sig.decl.implicit_self.has_implicit_self()), usize::from(sig.decl.implicit_self.has_implicit_self()),
) )
} else { } else {
@ -390,7 +390,6 @@ fn has_matching_args(kind: FnKind, args: GenericArgsRef<'_>) -> bool {
GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx), GenericArgKind::Type(ty) => matches!(*ty.kind(), ty::Param(ty) if ty.index as usize == idx),
GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx), GenericArgKind::Const(c) => matches!(c.kind(), ConstKind::Param(c) if c.index as usize == idx),
}), }),
#[allow(trivial_casts)] FnKind::ImplTraitFn(expected_args) => std::ptr::from_ref(args) as usize == expected_args,
FnKind::ImplTraitFn(expected_args) => args as *const _ as usize == expected_args,
} }
} }

View file

@ -771,6 +771,7 @@ declare_clippy_lint! {
pub struct Operators { pub struct Operators {
arithmetic_context: numeric_arithmetic::Context, arithmetic_context: numeric_arithmetic::Context,
verbose_bit_mask_threshold: u64, verbose_bit_mask_threshold: u64,
modulo_arithmetic_allow_comparison_to_zero: bool,
} }
impl_lint_pass!(Operators => [ impl_lint_pass!(Operators => [
ABSURD_EXTREME_COMPARISONS, ABSURD_EXTREME_COMPARISONS,
@ -801,10 +802,11 @@ impl_lint_pass!(Operators => [
SELF_ASSIGNMENT, SELF_ASSIGNMENT,
]); ]);
impl Operators { impl Operators {
pub fn new(verbose_bit_mask_threshold: u64) -> Self { pub fn new(verbose_bit_mask_threshold: u64, modulo_arithmetic_allow_comparison_to_zero: bool) -> Self {
Self { Self {
arithmetic_context: numeric_arithmetic::Context::default(), arithmetic_context: numeric_arithmetic::Context::default(),
verbose_bit_mask_threshold, verbose_bit_mask_threshold,
modulo_arithmetic_allow_comparison_to_zero,
} }
} }
} }
@ -835,12 +837,19 @@ impl<'tcx> LateLintPass<'tcx> for Operators {
cmp_owned::check(cx, op.node, lhs, rhs); cmp_owned::check(cx, op.node, lhs, rhs);
float_cmp::check(cx, e, op.node, lhs, rhs); float_cmp::check(cx, e, op.node, lhs, rhs);
modulo_one::check(cx, e, op.node, rhs); modulo_one::check(cx, e, op.node, rhs);
modulo_arithmetic::check(cx, e, op.node, lhs, rhs); modulo_arithmetic::check(
cx,
e,
op.node,
lhs,
rhs,
self.modulo_arithmetic_allow_comparison_to_zero,
);
}, },
ExprKind::AssignOp(op, lhs, rhs) => { ExprKind::AssignOp(op, lhs, rhs) => {
self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs); self.arithmetic_context.check_binary(cx, e, op.node, lhs, rhs);
misrefactored_assign_op::check(cx, e, op.node, lhs, rhs); misrefactored_assign_op::check(cx, e, op.node, lhs, rhs);
modulo_arithmetic::check(cx, e, op.node, lhs, rhs); modulo_arithmetic::check(cx, e, op.node, lhs, rhs, false);
}, },
ExprKind::Assign(lhs, rhs, _) => { ExprKind::Assign(lhs, rhs, _) => {
assign_op_pattern::check(cx, e, lhs, rhs); assign_op_pattern::check(cx, e, lhs, rhs);

View file

@ -1,7 +1,7 @@
use clippy_utils::consts::{constant, Constant}; use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sext; use clippy_utils::sext;
use rustc_hir::{BinOpKind, Expr}; use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use std::fmt::Display; use std::fmt::Display;
@ -14,8 +14,13 @@ pub(super) fn check<'tcx>(
op: BinOpKind, op: BinOpKind,
lhs: &'tcx Expr<'_>, lhs: &'tcx Expr<'_>,
rhs: &'tcx Expr<'_>, rhs: &'tcx Expr<'_>,
allow_comparison_to_zero: bool,
) { ) {
if op == BinOpKind::Rem { if op == BinOpKind::Rem {
if allow_comparison_to_zero && used_in_comparison_with_zero(cx, e) {
return;
}
let lhs_operand = analyze_operand(lhs, cx, e); let lhs_operand = analyze_operand(lhs, cx, e);
let rhs_operand = analyze_operand(rhs, cx, e); let rhs_operand = analyze_operand(rhs, cx, e);
if let Some(lhs_operand) = lhs_operand if let Some(lhs_operand) = lhs_operand
@ -28,6 +33,26 @@ pub(super) fn check<'tcx>(
}; };
} }
fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let Some(Node::Expr(parent_expr)) = cx.tcx.hir().find_parent(expr.hir_id) else {
return false;
};
let ExprKind::Binary(op, lhs, rhs) = parent_expr.kind else {
return false;
};
if op.node == BinOpKind::Eq || op.node == BinOpKind::Ne {
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), rhs) {
return true;
}
if let Some(Constant::Int(0)) = constant(cx, cx.typeck_results(), lhs) {
return true;
}
}
false
}
struct OperandInfo { struct OperandInfo {
string_representation: Option<String>, string_representation: Option<String>,
is_negative: bool, is_negative: bool,

View file

@ -2,6 +2,7 @@ use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::get_parent_expr; use clippy_utils::get_parent_expr;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use hir::Param;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor}; use rustc_hir::intravisit::{Visitor as HirVisitor, Visitor};
@ -13,6 +14,7 @@ use rustc_middle::hir::nested_filter;
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::ExpnKind;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -89,7 +91,12 @@ fn find_innermost_closure<'tcx>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
mut expr: &'tcx hir::Expr<'tcx>, mut expr: &'tcx hir::Expr<'tcx>,
mut steps: usize, mut steps: usize,
) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::FnDecl<'tcx>, ty::Asyncness)> { ) -> Option<(
&'tcx hir::Expr<'tcx>,
&'tcx hir::FnDecl<'tcx>,
ty::Asyncness,
&'tcx [Param<'tcx>],
)> {
let mut data = None; let mut data = None;
while let hir::ExprKind::Closure(closure) = expr.kind while let hir::ExprKind::Closure(closure) = expr.kind
@ -110,6 +117,7 @@ fn find_innermost_closure<'tcx>(
} else { } else {
ty::Asyncness::No ty::Asyncness::No
}, },
body.params,
)); ));
steps -= 1; steps -= 1;
} }
@ -152,7 +160,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
// without this check, we'd end up linting twice. // without this check, we'd end up linting twice.
&& !matches!(recv.kind, hir::ExprKind::Call(..)) && !matches!(recv.kind, hir::ExprKind::Call(..))
&& let (full_expr, call_depth) = get_parent_call_exprs(cx, expr) && let (full_expr, call_depth) = get_parent_call_exprs(cx, expr)
&& let Some((body, fn_decl, coroutine_kind)) = find_innermost_closure(cx, recv, call_depth) && let Some((body, fn_decl, coroutine_kind, params)) = find_innermost_closure(cx, recv, call_depth)
// outside macros we lint properly. Inside macros, we lint only ||() style closures.
&& (!matches!(expr.span.ctxt().outer_expn_data().kind, ExpnKind::Macro(_, _)) || params.is_empty())
{ {
span_lint_and_then( span_lint_and_then(
cx, cx,

View file

@ -4,8 +4,10 @@ use clippy_utils::ty::needs_ordered_drop;
use rustc_ast::Mutability; use rustc_ast::Mutability;
use rustc_hir::def::Res; use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, ByRef, ExprKind, HirId, Local, Node, Pat, PatKind, QPath}; use rustc_hir::{BindingAnnotation, ByRef, ExprKind, HirId, Local, Node, Pat, PatKind, QPath};
use rustc_hir_typeck::expr_use_visitor::PlaceBase;
use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::UpvarCapture;
use rustc_session::declare_lint_pass; use rustc_session::declare_lint_pass;
use rustc_span::symbol::Ident; use rustc_span::symbol::Ident;
use rustc_span::DesugaringKind; use rustc_span::DesugaringKind;
@ -69,6 +71,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
// the local is user-controlled // the local is user-controlled
&& !in_external_macro(cx.sess(), local.span) && !in_external_macro(cx.sess(), local.span)
&& !is_from_proc_macro(cx, expr) && !is_from_proc_macro(cx, expr)
&& !is_by_value_closure_capture(cx, local.hir_id, binding_id)
{ {
span_lint_and_help( span_lint_and_help(
cx, cx,
@ -82,6 +85,29 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
} }
} }
/// Checks if the enclosing body is a closure and if the given local is captured by value.
///
/// In those cases, the redefinition may be necessary to force a move:
/// ```
/// fn assert_static<T: 'static>(_: T) {}
///
/// let v = String::new();
/// let closure = || {
/// let v = v; // <- removing this redefinition makes `closure` no longer `'static`
/// dbg!(&v);
/// };
/// assert_static(closure);
/// ```
fn is_by_value_closure_capture(cx: &LateContext<'_>, redefinition: HirId, root_variable: HirId) -> bool {
let closure_def_id = cx.tcx.hir().enclosing_body_owner(redefinition);
cx.tcx.is_closure_or_coroutine(closure_def_id.to_def_id())
&& cx.tcx.closure_captures(closure_def_id).iter().any(|c| {
matches!(c.info.capture_kind, UpvarCapture::ByValue)
&& matches!(c.place.base, PlaceBase::Upvar(upvar) if upvar.var_path.hir_id == root_variable)
})
}
/// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced. /// Find the annotation of a binding introduced by a pattern, or `None` if it's not introduced.
fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> { fn find_binding(pat: &Pat<'_>, name: Ident) -> Option<BindingAnnotation> {
let mut ret = None; let mut ret = None;

View file

@ -188,7 +188,6 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
match init_lit.node { match init_lit.node {
// In these cases the annotation is redundant // In these cases the annotation is redundant
LitKind::Str(..) LitKind::Str(..)
| LitKind::ByteStr(..)
| LitKind::Byte(..) | LitKind::Byte(..)
| LitKind::Char(..) | LitKind::Char(..)
| LitKind::Bool(..) | LitKind::Bool(..)
@ -202,6 +201,16 @@ impl LateLintPass<'_> for RedundantTypeAnnotations {
} }
}, },
LitKind::Err => (), LitKind::Err => (),
LitKind::ByteStr(..) => {
// We only lint if the type annotation is an array type (e.g. &[u8; 4]).
// If instead it is a slice (e.g. &[u8]) it may not be redundant, so we
// don't lint.
if let hir::TyKind::Ref(_, mut_ty) = ty.kind
&& matches!(mut_ty.ty.kind, hir::TyKind::Array(..))
{
span_lint(cx, REDUNDANT_TYPE_ANNOTATIONS, local.span, "redundant type annotation");
}
},
} }
}, },
_ => (), _ => (),

View file

@ -42,7 +42,7 @@ declare_clippy_lint! {
/// // ^^^ this closure executes 123 times /// // ^^^ this closure executes 123 times
/// // and the vecs will have the expected capacity /// // and the vecs will have the expected capacity
/// ``` /// ```
#[clippy::version = "1.74.0"] #[clippy::version = "1.76.0"]
pub REPEAT_VEC_WITH_CAPACITY, pub REPEAT_VEC_WITH_CAPACITY,
suspicious, suspicious,
"repeating a `Vec::with_capacity` expression which does not retain capacity" "repeating a `Vec::with_capacity` expression which does not retain capacity"

View file

@ -2,10 +2,14 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin
use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{fn_def_id, is_from_proc_macro, is_inside_let_else, path_to_local_id, span_find_starting_semi}; use clippy_utils::{
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id,
span_find_starting_semi,
};
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::{ use rustc_hir::{
Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt, Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt,
StmtKind, StmtKind,
@ -18,6 +22,7 @@ use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId; use rustc_span::def_id::LocalDefId;
use rustc_span::{BytePos, Pos, Span}; use rustc_span::{BytePos, Pos, Span};
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Display;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -146,14 +151,14 @@ impl<'tcx> RetReplacement<'tcx> {
} }
} }
impl<'tcx> ToString for RetReplacement<'tcx> { impl<'tcx> Display for RetReplacement<'tcx> {
fn to_string(&self) -> String { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Empty => String::new(), Self::Empty => write!(f, ""),
Self::Block => "{}".to_string(), Self::Block => write!(f, "{{}}"),
Self::Unit => "()".to_string(), Self::Unit => write!(f, "()"),
Self::IfSequence(inner, _) => format!("({inner})"), Self::IfSequence(inner, _) => write!(f, "({inner})"),
Self::Expr(inner, _) => inner.to_string(), Self::Expr(inner, _) => write!(f, "{inner}"),
} }
} }
} }
@ -181,7 +186,15 @@ impl<'tcx> LateLintPass<'tcx> for Return {
if !in_external_macro(cx.sess(), stmt.span) if !in_external_macro(cx.sess(), stmt.span)
&& let StmtKind::Semi(expr) = stmt.kind && let StmtKind::Semi(expr) = stmt.kind
&& let ExprKind::Ret(Some(ret)) = expr.kind && let ExprKind::Ret(Some(ret)) = expr.kind
&& let ExprKind::Match(.., MatchSource::TryDesugar(_)) = ret.kind // return Err(...)? desugars to a match
// over a Err(...).branch()
// which breaks down to a branch call, with the callee being
// the constructor of the Err variant
&& let ExprKind::Match(maybe_cons, _, MatchSource::TryDesugar(_)) = ret.kind
&& let ExprKind::Call(_, [maybe_result_err]) = maybe_cons.kind
&& let ExprKind::Call(maybe_constr, _) = maybe_result_err.kind
&& is_res_lang_ctor(cx, path_res(cx, maybe_constr), ResultErr)
// Ensure this is not the final stmt, otherwise removing it would cause a compile error // Ensure this is not the final stmt, otherwise removing it would cause a compile error
&& let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir().get_parent_item(expr.hir_id)) && let OwnerNode::Item(item) = cx.tcx.hir_owner_node(cx.tcx.hir().get_parent_item(expr.hir_id))
&& let ItemKind::Fn(_, _, body) = item.kind && let ItemKind::Fn(_, _, body) = item.kind

View file

@ -0,0 +1,67 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for direct implementations of `ToString`.
/// ### Why is this bad?
/// This trait is automatically implemented for any type which implements the `Display` trait.
/// As such, `ToString` shouldnt be implemented directly: `Display` should be implemented instead,
/// and you get the `ToString` implementation for free.
/// ### Example
/// ```no_run
/// struct Point {
/// x: usize,
/// y: usize,
/// }
///
/// impl ToString for Point {
/// fn to_string(&self) -> String {
/// format!("({}, {})", self.x, self.y)
/// }
/// }
/// ```
/// Use instead:
/// ```no_run
/// struct Point {
/// x: usize,
/// y: usize,
/// }
///
/// impl std::fmt::Display for Point {
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
/// write!(f, "({}, {})", self.x, self.y)
/// }
/// }
/// ```
#[clippy::version = "1.77.0"]
pub TO_STRING_TRAIT_IMPL,
style,
"check for direct implementations of `ToString`"
}
declare_lint_pass!(ToStringTraitImpl => [TO_STRING_TRAIT_IMPL]);
impl<'tcx> LateLintPass<'tcx> for ToStringTraitImpl {
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'tcx>) {
if let ItemKind::Impl(Impl {
of_trait: Some(trait_ref),
..
}) = it.kind
&& let Some(trait_did) = trait_ref.trait_def_id()
&& cx.tcx.is_diagnostic_item(sym::ToString, trait_did)
{
span_lint_and_help(
cx,
TO_STRING_TRAIT_IMPL,
it.span,
"direct implementation of `ToString`",
None,
"prefer implementing `Display` instead",
);
}
}
}

View file

@ -69,14 +69,6 @@ fn span_error(cx: &LateContext<'_>, method_span: Span, expr: &Expr<'_>) {
); );
} }
fn get_ty_def_id(ty: Ty<'_>) -> Option<DefId> {
match ty.peel_refs().kind() {
ty::Adt(adt, _) => Some(adt.did()),
ty::Foreign(def_id) => Some(*def_id),
_ => None,
}
}
fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> { fn get_hir_ty_def_id<'tcx>(tcx: TyCtxt<'tcx>, hir_ty: rustc_hir::Ty<'tcx>) -> Option<DefId> {
let TyKind::Path(qpath) = hir_ty.kind else { return None }; let TyKind::Path(qpath) = hir_ty.kind else { return None };
match qpath { match qpath {
@ -131,21 +123,49 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt
} }
} }
#[allow(clippy::unnecessary_def_path)] /// When we have `x == y` where `x = &T` and `y = &T`, then that resolves to
/// `<&T as PartialEq<&T>>::eq`, which is not the same as `<T as PartialEq<T>>::eq`,
/// however we still would want to treat it the same, because we know that it's a blanket impl
/// that simply delegates to the `PartialEq` impl with one reference removed.
///
/// Still, we can't just do `lty.peel_refs() == rty.peel_refs()` because when we have `x = &T` and
/// `y = &&T`, this is not necessarily the same as `<T as PartialEq<T>>::eq`
///
/// So to avoid these FNs and FPs, we keep removing a layer of references from *both* sides
/// until both sides match the expected LHS and RHS type (or they don't).
fn matches_ty<'tcx>(
mut left: Ty<'tcx>,
mut right: Ty<'tcx>,
expected_left: Ty<'tcx>,
expected_right: Ty<'tcx>,
) -> bool {
while let (&ty::Ref(_, lty, _), &ty::Ref(_, rty, _)) = (left.kind(), right.kind()) {
if lty == expected_left && rty == expected_right {
return true;
}
left = lty;
right = rty;
}
false
}
fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) { fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: LocalDefId, name: Ident, expr: &Expr<'_>) {
let args = cx let Some(sig) = cx
.tcx .typeck_results()
.instantiate_bound_regions_with_erased(cx.tcx.fn_sig(method_def_id).skip_binder()) .liberated_fn_sigs()
.inputs(); .get(cx.tcx.local_def_id_to_hir_id(method_def_id))
else {
return;
};
// That has two arguments. // That has two arguments.
if let [self_arg, other_arg] = args if let [self_arg, other_arg] = sig.inputs()
&& let Some(self_arg) = get_ty_def_id(*self_arg) && let &ty::Ref(_, self_arg, _) = self_arg.kind()
&& let Some(other_arg) = get_ty_def_id(*other_arg) && let &ty::Ref(_, other_arg, _) = other_arg.kind()
// The two arguments are of the same type. // The two arguments are of the same type.
&& self_arg == other_arg
&& let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id) && let Some(trait_def_id) = get_impl_trait_def_id(cx, method_def_id)
// The trait is `PartialEq`. // The trait is `PartialEq`.
&& Some(trait_def_id) == get_trait_def_id(cx, &["core", "cmp", "PartialEq"]) && cx.tcx.is_diagnostic_item(sym::PartialEq, trait_def_id)
{ {
let to_check_op = if name.name == sym::eq { let to_check_op = if name.name == sym::eq {
BinOpKind::Eq BinOpKind::Eq
@ -154,31 +174,19 @@ fn check_partial_eq(cx: &LateContext<'_>, method_span: Span, method_def_id: Loca
}; };
let is_bad = match expr.kind { let is_bad = match expr.kind {
ExprKind::Binary(op, left, right) if op.node == to_check_op => { ExprKind::Binary(op, left, right) if op.node == to_check_op => {
// Then we check if the left-hand element is of the same type as `self`. // Then we check if the LHS matches self_arg and RHS matches other_arg
if let Some(left_ty) = cx.typeck_results().expr_ty_opt(left) let left_ty = cx.typeck_results().expr_ty_adjusted(left);
&& let Some(left_id) = get_ty_def_id(left_ty) let right_ty = cx.typeck_results().expr_ty_adjusted(right);
&& self_arg == left_id matches_ty(left_ty, right_ty, self_arg, other_arg)
&& let Some(right_ty) = cx.typeck_results().expr_ty_opt(right)
&& let Some(right_id) = get_ty_def_id(right_ty)
&& other_arg == right_id
{
true
} else {
false
}
}, },
ExprKind::MethodCall(segment, receiver, &[_arg], _) if segment.ident.name == name.name => { ExprKind::MethodCall(segment, receiver, [arg], _) if segment.ident.name == name.name => {
if let Some(ty) = cx.typeck_results().expr_ty_opt(receiver) let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver);
&& let Some(ty_id) = get_ty_def_id(ty) let arg_ty = cx.typeck_results().expr_ty_adjusted(arg);
&& self_arg != ty_id
{
// Since this called on a different type, the lint should not be
// triggered here.
return;
}
if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) if let Some(fn_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id) && let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
&& trait_id == trait_def_id && trait_id == trait_def_id
&& matches_ty(receiver_ty, arg_ty, self_arg, other_arg)
{ {
true true
} else { } else {

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths}; use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
use hir::{ExprKind, PatKind}; use hir::{ExprKind, PatKind};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -82,37 +83,72 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
} }
if let Some(exp) = block.expr if let Some(exp) = block.expr
&& matches!(exp.kind, hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, _)) && matches!(
exp.kind,
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
)
{ {
check_expr(cx, exp); check_expr(cx, exp);
} }
} }
} }
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
// if there is a guard, we consider the result to be consumed
if arm.guard.is_some() {
return false;
}
if is_unreachable_or_panic(cx, arm.body) {
// if the body is unreachable or there is a panic,
// we consider the result to be consumed
return false;
}
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
}
false
}
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
// if there is a guard, we consider the result to be consumed
if arm.guard.is_some() {
return false;
}
if is_unreachable_or_panic(cx, arm.body) {
// if the body is unreachable or there is a panic,
// we consider the result to be consumed
return false;
}
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
return true;
}
false
}
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) { fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
match expr.kind { match expr.kind {
hir::ExprKind::If(cond, _, _) hir::ExprKind::If(cond, _, _)
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
&& pattern_is_ignored_ok(cx, pat) && is_ok_wild_or_dotdot_pattern(cx, pat)
&& let Some(op) = should_lint(cx, init) => && let Some(op) = should_lint(cx, init) =>
{ {
emit_lint(cx, cond.span, op, &[pat.span]); emit_lint(cx, cond.span, op, &[pat.span]);
}, },
hir::ExprKind::Match(expr, arms, hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => { // we will capture only the case where the match is Ok( ) or Err( )
let found_arms: Vec<_> = arms // prefer to match the minimum possible, and expand later if needed
.iter() // to avoid false positives on something as used as this
.filter_map(|arm| { hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
if pattern_is_ignored_ok(cx, arm.pat) { if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
Some(arm.span) emit_lint(cx, expr.span, op, &[arm1.pat.span]);
} else { }
None if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
} emit_lint(cx, expr.span, op, &[arm2.pat.span]);
})
.collect();
if !found_arms.is_empty() {
emit_lint(cx, expr.span, op, found_arms.as_slice());
} }
}, },
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
_ if let Some(op) = should_lint(cx, expr) => { _ if let Some(op) = should_lint(cx, expr) => {
emit_lint(cx, expr.span, op, &[]); emit_lint(cx, expr.span, op, &[]);
}, },
@ -130,25 +166,40 @@ fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option
check_io_mode(cx, inner) check_io_mode(cx, inner)
} }
fn pattern_is_ignored_ok(cx: &LateContext<'_>, pat: &hir::Pat<'_>) -> bool { fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
// the if checks whether we are in a result Ok( ) pattern // the if checks whether we are in a result Ok( ) pattern
// and the return checks whether it is unhandled // and the return checks whether it is unhandled
if let PatKind::TupleStruct(ref path, inner_pat, ddp) = pat.kind if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
// we check against Result::Ok to avoid linting on Err(_) or something else. // we check against Result::Ok to avoid linting on Err(_) or something else.
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk) && is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
{ {
return match (inner_pat, ddp.as_opt_usize()) { if matches!(inner_pat, []) {
// Ok(_) pattern return true;
([inner_pat], None) if matches!(inner_pat.kind, PatKind::Wild) => true, }
// Ok(..) pattern
([], Some(0)) => true, if let [cons_pat] = inner_pat
_ => false, && matches!(cons_pat.kind, PatKind::Wild)
}; {
return true;
}
return false;
} }
false false
} }
// this is partially taken from panic_unimplemented
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
let expr = peel_blocks(expr);
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
return false;
};
if is_panic(cx, macro_call.def_id) {
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
}
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
}
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind { while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
if matches!( if matches!(

View file

@ -490,9 +490,9 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
format!("ClosureKind::Coroutine(CoroutineKind::Coroutine(Movability::{movability:?})") format!("ClosureKind::Coroutine(CoroutineKind::Coroutine(Movability::{movability:?})")
}, },
}, },
ClosureKind::CoroutineClosure(desugaring) => format!( ClosureKind::CoroutineClosure(desugaring) => {
"ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})" format!("ClosureKind::CoroutineClosure(CoroutineDesugaring::{desugaring:?})")
), },
}; };
let ret_ty = match fn_decl.output { let ret_ty = match fn_decl.output {

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_test_module_or_function; use clippy_utils::is_test_module_or_function;
use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::source::{snippet, snippet_with_applicability};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Item, ItemKind, PathSegment, UseKind}; use rustc_hir::{Item, ItemKind, PathSegment, UseKind};
@ -100,13 +101,15 @@ declare_clippy_lint! {
pub struct WildcardImports { pub struct WildcardImports {
warn_on_all: bool, warn_on_all: bool,
test_modules_deep: u32, test_modules_deep: u32,
allowed_segments: FxHashSet<String>,
} }
impl WildcardImports { impl WildcardImports {
pub fn new(warn_on_all: bool) -> Self { pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet<String>) -> Self {
Self { Self {
warn_on_all, warn_on_all,
test_modules_deep: 0, test_modules_deep: 0,
allowed_segments: allowed_wildcard_imports,
} }
} }
} }
@ -190,6 +193,7 @@ impl WildcardImports {
item.span.from_expansion() item.span.from_expansion()
|| is_prelude_import(segments) || is_prelude_import(segments)
|| (is_super_only_import(segments) && self.test_modules_deep > 0) || (is_super_only_import(segments) && self.test_modules_deep > 0)
|| is_allowed_via_config(segments, &self.allowed_segments)
} }
} }
@ -198,10 +202,18 @@ impl WildcardImports {
fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool { fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool {
segments segments
.iter() .iter()
.any(|ps| ps.ident.name.as_str().contains(sym::prelude.as_str())) .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str()))
} }
// Allow "super::*" imports in tests. // Allow "super::*" imports in tests.
fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool { fn is_super_only_import(segments: &[PathSegment<'_>]) -> bool {
segments.len() == 1 && segments[0].ident.name == kw::Super segments.len() == 1 && segments[0].ident.name == kw::Super
} }
// Allow skipping imports containing user configured segments,
// i.e. "...::utils::...::*" if user put `allowed-wildcard-imports = ["utils"]` in `Clippy.toml`
fn is_allowed_via_config(segments: &[PathSegment<'_>], allowed_segments: &FxHashSet<String>) -> bool {
// segment matching need to be exact instead of using 'contains', in case user unintentionaly put
// a single character in the config thus skipping most of the warnings.
segments.iter().any(|seg| allowed_segments.contains(seg.ident.as_str()))
}

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_utils" name = "clippy_utils"
version = "0.1.77" version = "0.1.78"
edition = "2021" edition = "2021"
publish = false publish = false

View file

@ -1,5 +1,6 @@
#![feature(array_chunks)] #![feature(array_chunks)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(control_flow_enum)]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(let_chains)] #![feature(let_chains)]
#![feature(lint_reasons)] #![feature(lint_reasons)]
@ -75,6 +76,7 @@ use core::mem;
use core::ops::ControlFlow; use core::ops::ControlFlow;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
use std::iter::{once, repeat};
use std::sync::{Mutex, MutexGuard, OnceLock}; use std::sync::{Mutex, MutexGuard, OnceLock};
use itertools::Itertools; use itertools::Itertools;
@ -84,6 +86,7 @@ use rustc_data_structures::packed::Pu128;
use rustc_data_structures::unhash::UnhashMap; use rustc_data_structures::unhash::UnhashMap;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId, LocalModDefId, LOCAL_CRATE};
use rustc_hir::definitions::{DefPath, DefPathData};
use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::hir_id::{HirIdMap, HirIdSet};
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
@ -102,8 +105,8 @@ use rustc_middle::ty::binding::BindingMode;
use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::fast_reject::SimplifiedType;
use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::{ use rustc_middle::ty::{
self as rustc_ty, Binder, BorrowKind, ClosureKind, FloatTy, IntTy, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeAndMut, self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, ParamEnv,
TypeVisitableExt, UintTy, UpvarCapture, ParamEnvAnd, Ty, TyCtxt, TypeAndMut, TypeVisitableExt, UintTy, UpvarCapture,
}; };
use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::SourceMap; use rustc_span::source_map::SourceMap;
@ -114,10 +117,7 @@ use visitors::Visitable;
use crate::consts::{constant, mir_to_const, Constant}; use crate::consts::{constant, mir_to_const, Constant};
use crate::higher::Range; use crate::higher::Range;
use crate::ty::{ use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type,
ty_is_fn_once_param,
};
use crate::visitors::for_each_expr; use crate::visitors::for_each_expr;
use rustc_middle::hir::nested_filter; use rustc_middle::hir::nested_filter;
@ -1353,46 +1353,12 @@ pub fn get_enclosing_loop_or_multi_call_closure<'tcx>(
for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) { for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) {
match node { match node {
Node::Expr(e) => match e.kind { Node::Expr(e) => match e.kind {
ExprKind::Closure { .. } => { ExprKind::Closure { .. }
if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind() if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind()
&& subs.as_closure().kind() == ClosureKind::FnOnce && subs.as_closure().kind() == ClosureKind::FnOnce => {},
{
continue; // Note: A closure's kind is determined by how it's used, not it's captures.
} ExprKind::Closure { .. } | ExprKind::Loop(..) => return Some(e),
let is_once = walk_to_expr_usage(cx, e, |node, id| {
let Node::Expr(e) = node else {
return None;
};
match e.kind {
ExprKind::Call(f, _) if f.hir_id == id => Some(()),
ExprKind::Call(f, args) => {
let i = args.iter().position(|arg| arg.hir_id == id)?;
let sig = expr_sig(cx, f)?;
let predicates = sig
.predicates_id()
.map_or(cx.param_env, |id| cx.tcx.param_env(id))
.caller_bounds();
sig.input(i).and_then(|ty| {
ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(())
})
},
ExprKind::MethodCall(_, receiver, args, _) => {
let i = std::iter::once(receiver)
.chain(args.iter())
.position(|arg| arg.hir_id == id)?;
let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?;
let ty = cx.tcx.fn_sig(id).instantiate_identity().skip_binder().inputs()[i];
ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(())
},
_ => None,
}
})
.is_some();
if !is_once {
return Some(e);
}
},
ExprKind::Loop(..) => return Some(e),
_ => (), _ => (),
}, },
Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (),
@ -2590,26 +2556,30 @@ pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool {
&& item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests")
} }
/// Walks the HIR tree from the given expression, up to the node where the value produced by the /// Walks up the HIR tree from the given expression in an attempt to find where the value is
/// expression is consumed. Calls the function for every node encountered this way until it returns /// consumed.
/// `Some`.
/// ///
/// This allows walking through `if`, `match`, `break`, block expressions to find where the value /// Termination has three conditions:
/// produced by the expression is consumed. /// - The given function returns `Break`. This function will return the value.
/// - The consuming node is found. This function will return `Continue(use_node, child_id)`.
/// - No further parent nodes are found. This will trigger a debug assert or return `None`.
///
/// This allows walking through `if`, `match`, `break`, and block expressions to find where the
/// value produced by the expression is consumed.
pub fn walk_to_expr_usage<'tcx, T>( pub fn walk_to_expr_usage<'tcx, T>(
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
e: &Expr<'tcx>, e: &Expr<'tcx>,
mut f: impl FnMut(Node<'tcx>, HirId) -> Option<T>, mut f: impl FnMut(HirId, Node<'tcx>, HirId) -> ControlFlow<T>,
) -> Option<T> { ) -> Option<ControlFlow<T, (Node<'tcx>, HirId)>> {
let map = cx.tcx.hir(); let map = cx.tcx.hir();
let mut iter = map.parent_iter(e.hir_id); let mut iter = map.parent_iter(e.hir_id);
let mut child_id = e.hir_id; let mut child_id = e.hir_id;
while let Some((parent_id, parent)) = iter.next() { while let Some((parent_id, parent)) = iter.next() {
if let Some(x) = f(parent, child_id) { if let ControlFlow::Break(x) = f(parent_id, parent, child_id) {
return Some(x); return Some(ControlFlow::Break(x));
} }
let parent = match parent { let parent_expr = match parent {
Node::Expr(e) => e, Node::Expr(e) => e,
Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => { Node::Block(Block { expr: Some(body), .. }) | Node::Arm(Arm { body, .. }) if body.hir_id == child_id => {
child_id = parent_id; child_id = parent_id;
@ -2619,18 +2589,19 @@ pub fn walk_to_expr_usage<'tcx, T>(
child_id = parent_id; child_id = parent_id;
continue; continue;
}, },
_ => return None, _ => return Some(ControlFlow::Continue((parent, child_id))),
}; };
match parent.kind { match parent_expr.kind {
ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id, ExprKind::If(child, ..) | ExprKind::Match(child, ..) if child.hir_id != child_id => child_id = parent_id,
ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => { ExprKind::Break(Destination { target_id: Ok(id), .. }, _) => {
child_id = id; child_id = id;
iter = map.parent_iter(id); iter = map.parent_iter(id);
}, },
ExprKind::Block(..) => child_id = parent_id, ExprKind::Block(..) | ExprKind::DropTemps(_) => child_id = parent_id,
_ => return None, _ => return Some(ControlFlow::Continue((parent, child_id))),
} }
} }
debug_assert!(false, "no parent node found for `{child_id:?}`");
None None
} }
@ -2674,6 +2645,8 @@ pub enum ExprUseNode<'tcx> {
Callee, Callee,
/// Access of a field. /// Access of a field.
FieldAccess(Ident), FieldAccess(Ident),
Expr,
Other,
} }
impl<'tcx> ExprUseNode<'tcx> { impl<'tcx> ExprUseNode<'tcx> {
/// Checks if the value is returned from the function. /// Checks if the value is returned from the function.
@ -2750,144 +2723,104 @@ impl<'tcx> ExprUseNode<'tcx> {
let sig = cx.tcx.fn_sig(id).skip_binder(); let sig = cx.tcx.fn_sig(id).skip_binder();
Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i))))
}, },
Self::Local(_) | Self::FieldAccess(..) | Self::Callee => None, Self::Local(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None,
} }
} }
} }
/// Gets the context an expression's value is used in. /// Gets the context an expression's value is used in.
#[expect(clippy::too_many_lines)]
pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> { pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option<ExprUseCtxt<'tcx>> {
let mut adjustments = [].as_slice(); let mut adjustments = [].as_slice();
let mut is_ty_unified = false; let mut is_ty_unified = false;
let mut moved_before_use = false; let mut moved_before_use = false;
let ctxt = e.span.ctxt(); let ctxt = e.span.ctxt();
walk_to_expr_usage(cx, e, &mut |parent, child_id| { walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| {
// LocalTableInContext returns the wrong lifetime, so go use `expr_adjustments` instead.
if adjustments.is_empty() if adjustments.is_empty()
&& let Node::Expr(e) = cx.tcx.hir_node(child_id) && let Node::Expr(e) = cx.tcx.hir_node(child_id)
{ {
adjustments = cx.typeck_results().expr_adjustments(e); adjustments = cx.typeck_results().expr_adjustments(e);
} }
match parent { if cx.tcx.hir().span(parent_id).ctxt() != ctxt {
Node::Local(l) if l.span.ctxt() == ctxt => Some(ExprUseCtxt { return ControlFlow::Break(());
node: ExprUseNode::Local(l), }
adjustments, if let Node::Expr(e) = parent {
is_ty_unified, match e.kind {
moved_before_use, ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
}), is_ty_unified = true;
moved_before_use = true;
},
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => {
is_ty_unified = true;
moved_before_use = true;
},
ExprKind::Block(..) => moved_before_use = true,
_ => {},
}
}
ControlFlow::Continue(())
})?
.continue_value()
.map(|(use_node, child_id)| {
let node = match use_node {
Node::Local(l) => ExprUseNode::Local(l),
Node::ExprField(field) => ExprUseNode::Field(field),
Node::Item(&Item { Node::Item(&Item {
kind: ItemKind::Static(..) | ItemKind::Const(..), kind: ItemKind::Static(..) | ItemKind::Const(..),
owner_id, owner_id,
span,
.. ..
}) })
| Node::TraitItem(&TraitItem { | Node::TraitItem(&TraitItem {
kind: TraitItemKind::Const(..), kind: TraitItemKind::Const(..),
owner_id, owner_id,
span,
.. ..
}) })
| Node::ImplItem(&ImplItem { | Node::ImplItem(&ImplItem {
kind: ImplItemKind::Const(..), kind: ImplItemKind::Const(..),
owner_id, owner_id,
span,
.. ..
}) if span.ctxt() == ctxt => Some(ExprUseCtxt { }) => ExprUseNode::ConstStatic(owner_id),
node: ExprUseNode::ConstStatic(owner_id),
adjustments,
is_ty_unified,
moved_before_use,
}),
Node::Item(&Item { Node::Item(&Item {
kind: ItemKind::Fn(..), kind: ItemKind::Fn(..),
owner_id, owner_id,
span,
.. ..
}) })
| Node::TraitItem(&TraitItem { | Node::TraitItem(&TraitItem {
kind: TraitItemKind::Fn(..), kind: TraitItemKind::Fn(..),
owner_id, owner_id,
span,
.. ..
}) })
| Node::ImplItem(&ImplItem { | Node::ImplItem(&ImplItem {
kind: ImplItemKind::Fn(..), kind: ImplItemKind::Fn(..),
owner_id, owner_id,
span,
.. ..
}) if span.ctxt() == ctxt => Some(ExprUseCtxt { }) => ExprUseNode::Return(owner_id),
node: ExprUseNode::Return(owner_id),
adjustments,
is_ty_unified,
moved_before_use,
}),
Node::ExprField(field) if field.span.ctxt() == ctxt => Some(ExprUseCtxt { Node::Expr(use_expr) => match use_expr.kind {
node: ExprUseNode::Field(field), ExprKind::Ret(_) => ExprUseNode::Return(OwnerId {
adjustments, def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
is_ty_unified,
moved_before_use,
}),
Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind {
ExprKind::Ret(_) => Some(ExprUseCtxt {
node: ExprUseNode::Return(OwnerId {
def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()),
}),
adjustments,
is_ty_unified,
moved_before_use,
}), }),
ExprKind::Closure(closure) => Some(ExprUseCtxt { ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }),
node: ExprUseNode::Return(OwnerId { def_id: closure.def_id }), ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) {
adjustments, Some(i) => ExprUseNode::FnArg(func, i),
is_ty_unified, None => ExprUseNode::Callee,
moved_before_use,
}),
ExprKind::Call(func, args) => Some(ExprUseCtxt {
node: match args.iter().position(|arg| arg.hir_id == child_id) {
Some(i) => ExprUseNode::FnArg(func, i),
None => ExprUseNode::Callee,
},
adjustments,
is_ty_unified,
moved_before_use,
}),
ExprKind::MethodCall(name, _, args, _) => Some(ExprUseCtxt {
node: ExprUseNode::MethodArg(
parent.hir_id,
name.args,
args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
),
adjustments,
is_ty_unified,
moved_before_use,
}),
ExprKind::Field(child, name) if child.hir_id == e.hir_id => Some(ExprUseCtxt {
node: ExprUseNode::FieldAccess(name),
adjustments,
is_ty_unified,
moved_before_use,
}),
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => {
is_ty_unified = true;
moved_before_use = true;
None
}, },
ExprKind::Block(_, Some(_)) | ExprKind::Break(..) => { ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg(
is_ty_unified = true; use_expr.hir_id,
moved_before_use = true; name.args,
None args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1),
}, ),
ExprKind::Block(..) => { ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name),
moved_before_use = true; _ => ExprUseNode::Expr,
None
},
_ => None,
}, },
_ => None, _ => ExprUseNode::Other,
};
ExprUseCtxt {
node,
adjustments,
is_ty_unified,
moved_before_use,
} }
}) })
} }
@ -3264,3 +3197,131 @@ pub fn is_never_expr<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> Option<
}) })
} }
} }
/// Produces a path from a local caller to the type of the called method. Suitable for user
/// output/suggestions.
///
/// Returned path can be either absolute (for methods defined non-locally), or relative (for local
/// methods).
pub fn get_path_from_caller_to_method_type<'tcx>(
tcx: TyCtxt<'tcx>,
from: LocalDefId,
method: DefId,
args: GenericArgsRef<'tcx>,
) -> String {
let assoc_item = tcx.associated_item(method);
let def_id = assoc_item.container_id(tcx);
match assoc_item.container {
rustc_ty::TraitContainer => get_path_to_callee(tcx, from, def_id),
rustc_ty::ImplContainer => {
let ty = tcx.type_of(def_id).instantiate_identity();
get_path_to_ty(tcx, from, ty, args)
},
}
}
fn get_path_to_ty<'tcx>(tcx: TyCtxt<'tcx>, from: LocalDefId, ty: Ty<'tcx>, args: GenericArgsRef<'tcx>) -> String {
match ty.kind() {
rustc_ty::Adt(adt, _) => get_path_to_callee(tcx, from, adt.did()),
// TODO these types need to be recursively resolved as well
rustc_ty::Array(..)
| rustc_ty::Dynamic(..)
| rustc_ty::Never
| rustc_ty::RawPtr(_)
| rustc_ty::Ref(..)
| rustc_ty::Slice(_)
| rustc_ty::Tuple(_) => format!("<{}>", EarlyBinder::bind(ty).instantiate(tcx, args)),
_ => ty.to_string(),
}
}
/// Produce a path from some local caller to the callee. Suitable for user output/suggestions.
fn get_path_to_callee(tcx: TyCtxt<'_>, from: LocalDefId, callee: DefId) -> String {
// only search for a relative path if the call is fully local
if callee.is_local() {
let callee_path = tcx.def_path(callee);
let caller_path = tcx.def_path(from.to_def_id());
maybe_get_relative_path(&caller_path, &callee_path, 2)
} else {
tcx.def_path_str(callee)
}
}
/// Tries to produce a relative path from `from` to `to`; if such a path would contain more than
/// `max_super` `super` items, produces an absolute path instead. Both `from` and `to` should be in
/// the local crate.
///
/// Suitable for user output/suggestions.
///
/// This ignores use items, and assumes that the target path is visible from the source
/// path (which _should_ be a reasonable assumption since we in order to be able to use an object of
/// certain type T, T is required to be visible).
///
/// TODO make use of `use` items. Maybe we should have something more sophisticated like
/// rust-analyzer does? <https://docs.rs/ra_ap_hir_def/0.0.169/src/ra_ap_hir_def/find_path.rs.html#19-27>
fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> String {
use itertools::EitherOrBoth::{Both, Left, Right};
// 1. skip the segments common for both paths (regardless of their type)
let unique_parts = to
.data
.iter()
.zip_longest(from.data.iter())
.skip_while(|el| matches!(el, Both(l, r) if l == r))
.map(|el| match el {
Both(l, r) => Both(l.data, r.data),
Left(l) => Left(l.data),
Right(r) => Right(r.data),
});
// 2. for the remaning segments, construct relative path using only mod names and `super`
let mut go_up_by = 0;
let mut path = Vec::new();
for el in unique_parts {
match el {
Both(l, r) => {
// consider:
// a::b::sym:: :: refers to
// c::d::e ::f::sym
// result should be super::super::c::d::e::f
//
// alternatively:
// a::b::c ::d::sym refers to
// e::f::sym:: ::
// result should be super::super::super::super::e::f
if let DefPathData::TypeNs(s) = l {
path.push(s.to_string());
}
if let DefPathData::TypeNs(_) = r {
go_up_by += 1;
}
},
// consider:
// a::b::sym:: :: refers to
// c::d::e ::f::sym
// when looking at `f`
Left(DefPathData::TypeNs(sym)) => path.push(sym.to_string()),
// consider:
// a::b::c ::d::sym refers to
// e::f::sym:: ::
// when looking at `d`
Right(DefPathData::TypeNs(_)) => go_up_by += 1,
_ => {},
}
}
if go_up_by > max_super {
// `super` chain would be too long, just use the absolute path instead
once(String::from("crate"))
.chain(to.data.iter().filter_map(|el| {
if let DefPathData::TypeNs(sym) = el.data {
Some(sym.to_string())
} else {
None
}
}))
.join("::")
} else {
repeat(String::from("super")).take(go_up_by).chain(path).join("::")
}
}

View file

@ -1000,35 +1000,6 @@ pub fn adt_and_variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<
} }
} }
/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`.
pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [ty::Clause<'_>]) -> bool {
let ty::Param(ty) = *ty.kind() else {
return false;
};
let lang = tcx.lang_items();
let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait())
else {
return false;
};
predicates
.iter()
.try_fold(false, |found, p| {
if let ty::ClauseKind::Trait(p) = p.kind().skip_binder()
&& let ty::Param(self_ty) = p.trait_ref.self_ty().kind()
&& ty.index == self_ty.index
{
// This should use `super_traits_of`, but that's a private function.
if p.trait_ref.def_id == fn_once_id {
return Some(true);
} else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id {
return None;
}
}
Some(found)
})
.unwrap_or(false)
}
/// Comes up with an "at least" guesstimate for the type's size, not taking into /// Comes up with an "at least" guesstimate for the type's size, not taking into
/// account the layout of type parameters. /// account the layout of type parameters.
pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "declare_clippy_lint" name = "declare_clippy_lint"
version = "0.1.77" version = "0.1.78"
edition = "2021" edition = "2021"
publish = false publish = false

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2024-01-25" channel = "nightly-2024-02-08"
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]

View file

@ -22,9 +22,11 @@ use rustc_session::EarlyDiagCtxt;
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use std::env; use std::env;
use std::fs::read_to_string;
use std::ops::Deref; use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::process::exit; use std::process::exit;
use std::string::ToString;
use anstream::println; use anstream::println;
@ -188,12 +190,31 @@ pub fn main() {
exit(rustc_driver::catch_with_exit_code(move || { exit(rustc_driver::catch_with_exit_code(move || {
let mut orig_args: Vec<String> = env::args().collect(); let mut orig_args: Vec<String> = env::args().collect();
let has_sysroot_arg = arg_value(&orig_args, "--sysroot", |_| true).is_some();
let has_sysroot_arg = |args: &mut [String]| -> bool {
if arg_value(args, "--sysroot", |_| true).is_some() {
return true;
}
// https://doc.rust-lang.org/rustc/command-line-arguments.html#path-load-command-line-flags-from-a-path
// Beside checking for existence of `--sysroot` on the command line, we need to
// check for the arg files that are prefixed with @ as well to be consistent with rustc
for arg in args.iter() {
if let Some(arg_file_path) = arg.strip_prefix('@') {
if let Ok(arg_file) = read_to_string(arg_file_path) {
let split_arg_file: Vec<String> = arg_file.lines().map(ToString::to_string).collect();
if arg_value(&split_arg_file, "--sysroot", |_| true).is_some() {
return true;
}
}
}
}
false
};
let sys_root_env = std::env::var("SYSROOT").ok(); let sys_root_env = std::env::var("SYSROOT").ok();
let pass_sysroot_env_if_given = |args: &mut Vec<String>, sys_root_env| { let pass_sysroot_env_if_given = |args: &mut Vec<String>, sys_root_env| {
if let Some(sys_root) = sys_root_env { if let Some(sys_root) = sys_root_env {
if !has_sysroot_arg { if !has_sysroot_arg(args) {
args.extend(vec!["--sysroot".into(), sys_root]); args.extend(vec!["--sysroot".into(), sys_root]);
} }
}; };

View file

@ -0,0 +1,45 @@
error: lint group `rust_2018_idioms` has the same priority (0) as a lint
--> Cargo.toml:7:1
|
7 | rust_2018_idioms = "warn"
| ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0
8 | bare_trait_objects = "allow"
| ------------------ has the same priority as this lint
|
= note: the order of the lints in the table is ignored by Cargo
= note: `#[deny(clippy::lint_groups_priority)]` on by default
help: to have lints override the group set `rust_2018_idioms` to a lower priority
|
7 | rust_2018_idioms = { level = "warn", priority = -1 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: lint group `unused` has the same priority (0) as a lint
--> Cargo.toml:10:1
|
10 | unused = { level = "deny" }
| ^^^^^^ ------------------ has an implicit priority of 0
11 | unused_braces = { level = "allow", priority = 1 }
12 | unused_attributes = { level = "allow" }
| ----------------- has the same priority as this lint
|
= note: the order of the lints in the table is ignored by Cargo
help: to have lints override the group set `unused` to a lower priority
|
10 | unused = { level = "deny", priority = -1 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: lint group `pedantic` has the same priority (-1) as a lint
--> Cargo.toml:19:1
|
19 | pedantic = { level = "warn", priority = -1 }
| ^^^^^^^^
20 | similar_names = { level = "allow", priority = -1 }
| ------------- has the same priority as this lint
|
= note: the order of the lints in the table is ignored by Cargo
help: to have lints override the group set `pedantic` to a lower priority
|
19 | pedantic = { level = "warn", priority = -2 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: could not compile `fail` (lib) due to 3 previous errors

View file

@ -0,0 +1,20 @@
[package]
name = "fail"
version = "0.1.0"
publish = false
[lints.rust]
rust_2018_idioms = "warn"
bare_trait_objects = "allow"
unused = { level = "deny" }
unused_braces = { level = "allow", priority = 1 }
unused_attributes = { level = "allow" }
# `warnings` is not a group so the order it is passed does not matter
warnings = "deny"
deprecated = "allow"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
similar_names = { level = "allow", priority = -1 }

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,10 @@
[package]
name = "pass"
version = "0.1.0"
publish = false
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
style = { level = "warn", priority = 1 }
similar_names = "allow"
dbg_macro = { level = "warn", priority = 2 }

View file

@ -0,0 +1 @@

View file

@ -1,4 +1,4 @@
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)] #![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)] #![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
use std::mem::{size_of, size_of_val}; use std::mem::{size_of, size_of_val};

View file

@ -1,4 +1,4 @@
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)] #![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)] #![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
use std::mem::{size_of, size_of_val}; use std::mem::{size_of, size_of_val};

View file

@ -0,0 +1 @@
allow-comparison-to-zero = false

View file

@ -0,0 +1,10 @@
#![warn(clippy::modulo_arithmetic)]
fn main() {
let a = -1;
let b = 2;
let c = a % b == 0;
let c = a % b != 0;
let c = 0 == a % b;
let c = 0 != a % b;
}

View file

@ -0,0 +1,40 @@
error: you are using modulo operator on types that might have different signs
--> $DIR/modulo_arithmetic.rs:6:13
|
LL | let c = a % b == 0;
| ^^^^^
|
= note: double check for expected result especially when interoperating with different languages
= note: or consider using `rem_euclid` or similar function
= note: `-D clippy::modulo-arithmetic` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::modulo_arithmetic)]`
error: you are using modulo operator on types that might have different signs
--> $DIR/modulo_arithmetic.rs:7:13
|
LL | let c = a % b != 0;
| ^^^^^
|
= note: double check for expected result especially when interoperating with different languages
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on types that might have different signs
--> $DIR/modulo_arithmetic.rs:8:18
|
LL | let c = 0 == a % b;
| ^^^^^
|
= note: double check for expected result especially when interoperating with different languages
= note: or consider using `rem_euclid` or similar function
error: you are using modulo operator on types that might have different signs
--> $DIR/modulo_arithmetic.rs:9:18
|
LL | let c = 0 != a % b;
| ^^^^^
|
= note: double check for expected result especially when interoperating with different languages
= note: or consider using `rem_euclid` or similar function
error: aborting due to 4 previous errors

View file

@ -3,6 +3,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
absolute-paths-max-segments absolute-paths-max-segments
accept-comment-above-attributes accept-comment-above-attributes
accept-comment-above-statement accept-comment-above-statement
allow-comparison-to-zero
allow-dbg-in-tests allow-dbg-in-tests
allow-expect-in-tests allow-expect-in-tests
allow-mixed-uninlined-format-args allow-mixed-uninlined-format-args
@ -14,6 +15,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
allowed-duplicate-crates allowed-duplicate-crates
allowed-idents-below-min-chars allowed-idents-below-min-chars
allowed-scripts allowed-scripts
allowed-wildcard-imports
arithmetic-side-effects-allowed arithmetic-side-effects-allowed
arithmetic-side-effects-allowed-binary arithmetic-side-effects-allowed-binary
arithmetic-side-effects-allowed-unary arithmetic-side-effects-allowed-unary
@ -80,6 +82,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
absolute-paths-max-segments absolute-paths-max-segments
accept-comment-above-attributes accept-comment-above-attributes
accept-comment-above-statement accept-comment-above-statement
allow-comparison-to-zero
allow-dbg-in-tests allow-dbg-in-tests
allow-expect-in-tests allow-expect-in-tests
allow-mixed-uninlined-format-args allow-mixed-uninlined-format-args
@ -91,6 +94,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
allowed-duplicate-crates allowed-duplicate-crates
allowed-idents-below-min-chars allowed-idents-below-min-chars
allowed-scripts allowed-scripts
allowed-wildcard-imports
arithmetic-side-effects-allowed arithmetic-side-effects-allowed
arithmetic-side-effects-allowed-binary arithmetic-side-effects-allowed-binary
arithmetic-side-effects-allowed-unary arithmetic-side-effects-allowed-unary
@ -157,6 +161,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
absolute-paths-max-segments absolute-paths-max-segments
accept-comment-above-attributes accept-comment-above-attributes
accept-comment-above-statement accept-comment-above-statement
allow-comparison-to-zero
allow-dbg-in-tests allow-dbg-in-tests
allow-expect-in-tests allow-expect-in-tests
allow-mixed-uninlined-format-args allow-mixed-uninlined-format-args
@ -168,6 +173,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
allowed-duplicate-crates allowed-duplicate-crates
allowed-idents-below-min-chars allowed-idents-below-min-chars
allowed-scripts allowed-scripts
allowed-wildcard-imports
arithmetic-side-effects-allowed arithmetic-side-effects-allowed
arithmetic-side-effects-allowed-binary arithmetic-side-effects-allowed-binary
arithmetic-side-effects-allowed-unary arithmetic-side-effects-allowed-unary

View file

@ -1 +1,4 @@
warn-on-all-wildcard-imports = true warn-on-all-wildcard-imports = true
# This should be ignored since `warn-on-all-wildcard-imports` has higher precedence
allowed-wildcard-imports = ["utils"]

View file

@ -3,9 +3,28 @@
mod prelude { mod prelude {
pub const FOO: u8 = 1; pub const FOO: u8 = 1;
} }
mod utils {
pub const BAR: u8 = 1;
pub fn print() {}
}
mod my_crate {
pub mod utils {
pub fn my_util_fn() {}
}
}
use utils::{BAR, print};
//~^ ERROR: usage of wildcard import
use my_crate::utils::my_util_fn;
//~^ ERROR: usage of wildcard import
use prelude::FOO; use prelude::FOO;
//~^ ERROR: usage of wildcard import //~^ ERROR: usage of wildcard import
fn main() { fn main() {
let _ = FOO; let _ = FOO;
let _ = BAR;
print();
my_util_fn();
} }

View file

@ -3,9 +3,28 @@
mod prelude { mod prelude {
pub const FOO: u8 = 1; pub const FOO: u8 = 1;
} }
mod utils {
pub const BAR: u8 = 1;
pub fn print() {}
}
mod my_crate {
pub mod utils {
pub fn my_util_fn() {}
}
}
use utils::*;
//~^ ERROR: usage of wildcard import
use my_crate::utils::*;
//~^ ERROR: usage of wildcard import
use prelude::*; use prelude::*;
//~^ ERROR: usage of wildcard import //~^ ERROR: usage of wildcard import
fn main() { fn main() {
let _ = FOO; let _ = FOO;
let _ = BAR;
print();
my_util_fn();
} }

View file

@ -1,11 +1,23 @@
error: usage of wildcard import error: usage of wildcard import
--> $DIR/wildcard_imports.rs:6:5 --> $DIR/wildcard_imports.rs:18:5
| |
LL | use prelude::*; LL | use utils::*;
| ^^^^^^^^^^ help: try: `prelude::FOO` | ^^^^^^^^ help: try: `utils::{BAR, print}`
| |
= note: `-D clippy::wildcard-imports` implied by `-D warnings` = note: `-D clippy::wildcard-imports` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]` = help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]`
error: aborting due to 1 previous error error: usage of wildcard import
--> $DIR/wildcard_imports.rs:20:5
|
LL | use my_crate::utils::*;
| ^^^^^^^^^^^^^^^^^^ help: try: `my_crate::utils::my_util_fn`
error: usage of wildcard import
--> $DIR/wildcard_imports.rs:22:5
|
LL | use prelude::*;
| ^^^^^^^^^^ help: try: `prelude::FOO`
error: aborting due to 3 previous errors

View file

@ -0,0 +1 @@
allowed-wildcard-imports = ["utils"]

View file

@ -0,0 +1,26 @@
#![warn(clippy::wildcard_imports)]
mod utils {
pub fn print() {}
}
mod utils_plus {
pub fn do_something() {}
}
mod my_crate {
pub mod utils {
pub fn my_util_fn() {}
}
}
use my_crate::utils::*;
use utils::*;
use utils_plus::do_something;
//~^ ERROR: usage of wildcard import
fn main() {
print();
my_util_fn();
do_something();
}

View file

@ -0,0 +1,26 @@
#![warn(clippy::wildcard_imports)]
mod utils {
pub fn print() {}
}
mod utils_plus {
pub fn do_something() {}
}
mod my_crate {
pub mod utils {
pub fn my_util_fn() {}
}
}
use my_crate::utils::*;
use utils::*;
use utils_plus::*;
//~^ ERROR: usage of wildcard import
fn main() {
print();
my_util_fn();
do_something();
}

View file

@ -0,0 +1,11 @@
error: usage of wildcard import
--> $DIR/wildcard_imports.rs:19:5
|
LL | use utils_plus::*;
| ^^^^^^^^^^^^^ help: try: `utils_plus::do_something`
|
= note: `-D clippy::wildcard-imports` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::wildcard_imports)]`
error: aborting due to 1 previous error

View file

@ -5,6 +5,7 @@ fn a() -> i32 {
0 0
} }
#[clippy::msrv = "1.75"]
fn main() { fn main() {
let val = 1; let val = 1;
let _p = std::ptr::addr_of!(val); let _p = std::ptr::addr_of!(val);

View file

@ -5,6 +5,7 @@ fn a() -> i32 {
0 0
} }
#[clippy::msrv = "1.75"]
fn main() { fn main() {
let val = 1; let val = 1;
let _p = &val as *const i32; let _p = &val as *const i32;

View file

@ -1,5 +1,5 @@
error: borrow as raw pointer error: borrow as raw pointer
--> $DIR/borrow_as_ptr.rs:10:14 --> $DIR/borrow_as_ptr.rs:11:14
| |
LL | let _p = &val as *const i32; LL | let _p = &val as *const i32;
| ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)` | ^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of!(val)`
@ -8,7 +8,7 @@ LL | let _p = &val as *const i32;
= help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]` = help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
error: borrow as raw pointer error: borrow as raw pointer
--> $DIR/borrow_as_ptr.rs:17:18 --> $DIR/borrow_as_ptr.rs:18:18
| |
LL | let _p_mut = &mut val_mut as *mut i32; LL | let _p_mut = &mut val_mut as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)` | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::ptr::addr_of_mut!(val_mut)`

View file

@ -2,6 +2,7 @@
#![feature(lang_items, start, libc)] #![feature(lang_items, start, libc)]
#![no_std] #![no_std]
#[clippy::msrv = "1.75"]
#[start] #[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize { fn main(_argc: isize, _argv: *const *const u8) -> isize {
let val = 1; let val = 1;

View file

@ -2,6 +2,7 @@
#![feature(lang_items, start, libc)] #![feature(lang_items, start, libc)]
#![no_std] #![no_std]
#[clippy::msrv = "1.75"]
#[start] #[start]
fn main(_argc: isize, _argv: *const *const u8) -> isize { fn main(_argc: isize, _argv: *const *const u8) -> isize {
let val = 1; let val = 1;

View file

@ -1,5 +1,5 @@
error: borrow as raw pointer error: borrow as raw pointer
--> $DIR/borrow_as_ptr_no_std.rs:8:14 --> $DIR/borrow_as_ptr_no_std.rs:9:14
| |
LL | let _p = &val as *const i32; LL | let _p = &val as *const i32;
| ^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of!(val)` | ^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of!(val)`
@ -8,7 +8,7 @@ LL | let _p = &val as *const i32;
= help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]` = help: to override `-D warnings` add `#[allow(clippy::borrow_as_ptr)]`
error: borrow as raw pointer error: borrow as raw pointer
--> $DIR/borrow_as_ptr_no_std.rs:11:18 --> $DIR/borrow_as_ptr_no_std.rs:12:18
| |
LL | let _p_mut = &mut val_mut as *mut i32; LL | let _p_mut = &mut val_mut as *mut i32;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of_mut!(val_mut)` | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `core::ptr::addr_of_mut!(val_mut)`

View file

@ -346,6 +346,23 @@ fn angle_brackets_and_args() {
dyn_opt.map(<dyn TestTrait>::method_on_dyn); dyn_opt.map(<dyn TestTrait>::method_on_dyn);
} }
// https://github.com/rust-lang/rust-clippy/issues/12199
fn track_caller_fp() {
struct S;
impl S {
#[track_caller]
fn add_location(self) {}
}
#[track_caller]
fn add_location() {}
fn foo(_: fn()) {}
fn foo2(_: fn(S)) {}
foo(|| add_location());
foo2(|s| s.add_location());
}
fn _late_bound_to_early_bound_regions() { fn _late_bound_to_early_bound_regions() {
struct Foo<'a>(&'a u32); struct Foo<'a>(&'a u32);
impl<'a> Foo<'a> { impl<'a> Foo<'a> {
@ -400,3 +417,57 @@ fn _closure_with_types() {
let _ = f2(|x: u32| f(x)); let _ = f2(|x: u32| f(x));
let _ = f2(|x| -> u32 { f(x) }); let _ = f2(|x| -> u32 { f(x) });
} }
/// https://github.com/rust-lang/rust-clippy/issues/10854
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
mod issue_10854 {
pub mod test_mod {
pub struct Test;
impl Test {
pub fn method(self) -> i32 {
0
}
}
pub fn calls_test(test: Option<Test>) -> Option<i32> {
test.map(Test::method)
}
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
test.map(super::Outer::method)
}
}
pub struct Outer;
impl Outer {
pub fn method(self) -> i32 {
0
}
}
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
test.map(test_mod::Test::method)
}
mod a {
pub mod b {
pub mod c {
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
test.map(crate::issue_10854::d::Test::method)
}
}
}
}
mod d {
pub struct Test;
impl Test {
pub fn method(self) -> i32 {
0
}
}
}
}

View file

@ -346,6 +346,23 @@ fn angle_brackets_and_args() {
dyn_opt.map(|d| d.method_on_dyn()); dyn_opt.map(|d| d.method_on_dyn());
} }
// https://github.com/rust-lang/rust-clippy/issues/12199
fn track_caller_fp() {
struct S;
impl S {
#[track_caller]
fn add_location(self) {}
}
#[track_caller]
fn add_location() {}
fn foo(_: fn()) {}
fn foo2(_: fn(S)) {}
foo(|| add_location());
foo2(|s| s.add_location());
}
fn _late_bound_to_early_bound_regions() { fn _late_bound_to_early_bound_regions() {
struct Foo<'a>(&'a u32); struct Foo<'a>(&'a u32);
impl<'a> Foo<'a> { impl<'a> Foo<'a> {
@ -400,3 +417,57 @@ fn _closure_with_types() {
let _ = f2(|x: u32| f(x)); let _ = f2(|x: u32| f(x));
let _ = f2(|x| -> u32 { f(x) }); let _ = f2(|x| -> u32 { f(x) });
} }
/// https://github.com/rust-lang/rust-clippy/issues/10854
/// This is to verify that redundant_closure_for_method_calls resolves suggested paths to relative.
mod issue_10854 {
pub mod test_mod {
pub struct Test;
impl Test {
pub fn method(self) -> i32 {
0
}
}
pub fn calls_test(test: Option<Test>) -> Option<i32> {
test.map(|t| t.method())
}
pub fn calls_outer(test: Option<super::Outer>) -> Option<i32> {
test.map(|t| t.method())
}
}
pub struct Outer;
impl Outer {
pub fn method(self) -> i32 {
0
}
}
pub fn calls_into_mod(test: Option<test_mod::Test>) -> Option<i32> {
test.map(|t| t.method())
}
mod a {
pub mod b {
pub mod c {
pub fn extreme_nesting(test: Option<super::super::super::d::Test>) -> Option<i32> {
test.map(|t| t.method())
}
}
}
}
mod d {
pub struct Test;
impl Test {
pub fn method(self) -> i32 {
0
}
}
}
}

View file

@ -161,10 +161,34 @@ LL | dyn_opt.map(|d| d.method_on_dyn());
| ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn` | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<dyn TestTrait>::method_on_dyn`
error: redundant closure error: redundant closure
--> $DIR/eta.rs:389:19 --> $DIR/eta.rs:406:19
| |
LL | let _ = f(&0, |x, y| f2(x, y)); LL | let _ = f(&0, |x, y| f2(x, y));
| ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2`
error: aborting due to 27 previous errors error: redundant closure
--> $DIR/eta.rs:434:22
|
LL | test.map(|t| t.method())
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method`
error: redundant closure
--> $DIR/eta.rs:438:22
|
LL | test.map(|t| t.method())
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method`
error: redundant closure
--> $DIR/eta.rs:451:18
|
LL | test.map(|t| t.method())
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method`
error: redundant closure
--> $DIR/eta.rs:458:30
|
LL | test.map(|t| t.method())
| ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method`
error: aborting due to 31 previous errors

View file

@ -14,6 +14,7 @@ use std::panic::Location;
struct Somewhere; struct Somewhere;
#[allow(clippy::to_string_trait_impl)]
impl ToString for Somewhere { impl ToString for Somewhere {
fn to_string(&self) -> String { fn to_string(&self) -> String {
String::from("somewhere") String::from("somewhere")

View file

@ -14,6 +14,7 @@ use std::panic::Location;
struct Somewhere; struct Somewhere;
#[allow(clippy::to_string_trait_impl)]
impl ToString for Somewhere { impl ToString for Somewhere {
fn to_string(&self) -> String { fn to_string(&self) -> String {
String::from("somewhere") String::from("somewhere")

View file

@ -1,5 +1,5 @@
error: `to_string` applied to a type that implements `Display` in `format!` args error: `to_string` applied to a type that implements `Display` in `format!` args
--> $DIR/format_args.rs:76:72 --> $DIR/format_args.rs:77:72
| |
LL | let _ = format!("error: something failed at {}", Location::caller().to_string()); LL | let _ = format!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
@ -8,145 +8,145 @@ LL | let _ = format!("error: something failed at {}", Location::caller().to_
= help: to override `-D warnings` add `#[allow(clippy::to_string_in_format_args)]` = help: to override `-D warnings` add `#[allow(clippy::to_string_in_format_args)]`
error: `to_string` applied to a type that implements `Display` in `write!` args error: `to_string` applied to a type that implements `Display` in `write!` args
--> $DIR/format_args.rs:80:27 --> $DIR/format_args.rs:81:27
| |
LL | Location::caller().to_string() LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `writeln!` args error: `to_string` applied to a type that implements `Display` in `writeln!` args
--> $DIR/format_args.rs:85:27 --> $DIR/format_args.rs:86:27
| |
LL | Location::caller().to_string() LL | Location::caller().to_string()
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `print!` args error: `to_string` applied to a type that implements `Display` in `print!` args
--> $DIR/format_args.rs:87:63 --> $DIR/format_args.rs:88:63
| |
LL | print!("error: something failed at {}", Location::caller().to_string()); LL | print!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:88:65 --> $DIR/format_args.rs:89:65
| |
LL | println!("error: something failed at {}", Location::caller().to_string()); LL | println!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprint!` args error: `to_string` applied to a type that implements `Display` in `eprint!` args
--> $DIR/format_args.rs:89:64 --> $DIR/format_args.rs:90:64
| |
LL | eprint!("error: something failed at {}", Location::caller().to_string()); LL | eprint!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `eprintln!` args error: `to_string` applied to a type that implements `Display` in `eprintln!` args
--> $DIR/format_args.rs:90:66 --> $DIR/format_args.rs:91:66
| |
LL | eprintln!("error: something failed at {}", Location::caller().to_string()); LL | eprintln!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `format_args!` args error: `to_string` applied to a type that implements `Display` in `format_args!` args
--> $DIR/format_args.rs:91:77 --> $DIR/format_args.rs:92:77
| |
LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string()); LL | let _ = format_args!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert!` args error: `to_string` applied to a type that implements `Display` in `assert!` args
--> $DIR/format_args.rs:92:70 --> $DIR/format_args.rs:93:70
| |
LL | assert!(true, "error: something failed at {}", Location::caller().to_string()); LL | assert!(true, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_eq!` args error: `to_string` applied to a type that implements `Display` in `assert_eq!` args
--> $DIR/format_args.rs:93:73 --> $DIR/format_args.rs:94:73
| |
LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string()); LL | assert_eq!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `assert_ne!` args error: `to_string` applied to a type that implements `Display` in `assert_ne!` args
--> $DIR/format_args.rs:94:73 --> $DIR/format_args.rs:95:73
| |
LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string()); LL | assert_ne!(0, 0, "error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `panic!` args error: `to_string` applied to a type that implements `Display` in `panic!` args
--> $DIR/format_args.rs:95:63 --> $DIR/format_args.rs:96:63
| |
LL | panic!("error: something failed at {}", Location::caller().to_string()); LL | panic!("error: something failed at {}", Location::caller().to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:96:20 --> $DIR/format_args.rs:97:20
| |
LL | println!("{}", X(1).to_string()); LL | println!("{}", X(1).to_string());
| ^^^^^^^^^^^^^^^^ help: use this: `*X(1)` | ^^^^^^^^^^^^^^^^ help: use this: `*X(1)`
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:97:20 --> $DIR/format_args.rs:98:20
| |
LL | println!("{}", Y(&X(1)).to_string()); LL | println!("{}", Y(&X(1)).to_string());
| ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))` | ^^^^^^^^^^^^^^^^^^^^ help: use this: `***Y(&X(1))`
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:98:24 --> $DIR/format_args.rs:99:24
| |
LL | println!("{}", Z(1).to_string()); LL | println!("{}", Z(1).to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:99:20 --> $DIR/format_args.rs:100:20
| |
LL | println!("{}", x.to_string()); LL | println!("{}", x.to_string());
| ^^^^^^^^^^^^^ help: use this: `**x` | ^^^^^^^^^^^^^ help: use this: `**x`
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:100:20 --> $DIR/format_args.rs:101:20
| |
LL | println!("{}", x_ref.to_string()); LL | println!("{}", x_ref.to_string());
| ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref` | ^^^^^^^^^^^^^^^^^ help: use this: `***x_ref`
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:102:39 --> $DIR/format_args.rs:103:39
| |
LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar"); LL | println!("{foo}{bar}", foo = "foo".to_string(), bar = "bar");
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:103:52 --> $DIR/format_args.rs:104:52
| |
LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string()); LL | println!("{foo}{bar}", foo = "foo", bar = "bar".to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:104:39 --> $DIR/format_args.rs:105:39
| |
LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo"); LL | println!("{foo}{bar}", bar = "bar".to_string(), foo = "foo");
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:105:52 --> $DIR/format_args.rs:106:52
| |
LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string()); LL | println!("{foo}{bar}", bar = "bar", foo = "foo".to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `print!` args error: `to_string` applied to a type that implements `Display` in `print!` args
--> $DIR/format_args.rs:117:37 --> $DIR/format_args.rs:118:37
| |
LL | print!("{}", (Location::caller().to_string())); LL | print!("{}", (Location::caller().to_string()));
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `print!` args error: `to_string` applied to a type that implements `Display` in `print!` args
--> $DIR/format_args.rs:118:39 --> $DIR/format_args.rs:119:39
| |
LL | print!("{}", ((Location::caller()).to_string())); LL | print!("{}", ((Location::caller()).to_string()));
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `format!` args error: `to_string` applied to a type that implements `Display` in `format!` args
--> $DIR/format_args.rs:146:38 --> $DIR/format_args.rs:147:38
| |
LL | let x = format!("{} {}", a, b.to_string()); LL | let x = format!("{} {}", a, b.to_string());
| ^^^^^^^^^^^^ help: remove this | ^^^^^^^^^^^^ help: remove this
error: `to_string` applied to a type that implements `Display` in `println!` args error: `to_string` applied to a type that implements `Display` in `println!` args
--> $DIR/format_args.rs:160:24 --> $DIR/format_args.rs:161:24
| |
LL | println!("{}", original[..10].to_string()); LL | println!("{}", original[..10].to_string());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use this: `&original[..10]` | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use this: `&original[..10]`

View file

@ -1,6 +1,11 @@
//@aux-build:proc_macro_derive.rs //@aux-build:proc_macro_derive.rs
#![warn(clippy::ignored_unit_patterns)] #![warn(clippy::ignored_unit_patterns)]
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)] #![allow(
clippy::let_unit_value,
clippy::redundant_pattern_matching,
clippy::single_match,
clippy::needless_borrow
)]
fn foo() -> Result<(), ()> { fn foo() -> Result<(), ()> {
unimplemented!() unimplemented!()

View file

@ -1,6 +1,11 @@
//@aux-build:proc_macro_derive.rs //@aux-build:proc_macro_derive.rs
#![warn(clippy::ignored_unit_patterns)] #![warn(clippy::ignored_unit_patterns)]
#![allow(clippy::let_unit_value, clippy::redundant_pattern_matching, clippy::single_match)] #![allow(
clippy::let_unit_value,
clippy::redundant_pattern_matching,
clippy::single_match,
clippy::needless_borrow
)]
fn foo() -> Result<(), ()> { fn foo() -> Result<(), ()> {
unimplemented!() unimplemented!()

View file

@ -1,5 +1,5 @@
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:11:12 --> $DIR/ignored_unit_patterns.rs:16:12
| |
LL | Ok(_) => {}, LL | Ok(_) => {},
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
@ -8,49 +8,49 @@ LL | Ok(_) => {},
= help: to override `-D warnings` add `#[allow(clippy::ignored_unit_patterns)]` = help: to override `-D warnings` add `#[allow(clippy::ignored_unit_patterns)]`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:12:13 --> $DIR/ignored_unit_patterns.rs:17:13
| |
LL | Err(_) => {}, LL | Err(_) => {},
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:14:15 --> $DIR/ignored_unit_patterns.rs:19:15
| |
LL | if let Ok(_) = foo() {} LL | if let Ok(_) = foo() {}
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:16:28 --> $DIR/ignored_unit_patterns.rs:21:28
| |
LL | let _ = foo().map_err(|_| todo!()); LL | let _ = foo().map_err(|_| todo!());
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:22:16 --> $DIR/ignored_unit_patterns.rs:27:16
| |
LL | Ok(_) => {}, LL | Ok(_) => {},
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:24:17 --> $DIR/ignored_unit_patterns.rs:29:17
| |
LL | Err(_) => {}, LL | Err(_) => {},
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:36:9 --> $DIR/ignored_unit_patterns.rs:41:9
| |
LL | let _ = foo().unwrap(); LL | let _ = foo().unwrap();
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:45:13 --> $DIR/ignored_unit_patterns.rs:50:13
| |
LL | (1, _) => unimplemented!(), LL | (1, _) => unimplemented!(),
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`
error: matching over `()` is more explicit error: matching over `()` is more explicit
--> $DIR/ignored_unit_patterns.rs:52:13 --> $DIR/ignored_unit_patterns.rs:57:13
| |
LL | for (x, _) in v { LL | for (x, _) in v {
| ^ help: use `()` instead of `_`: `()` | ^ help: use `()` instead of `_`: `()`

View file

@ -0,0 +1,23 @@
#![warn(clippy::incompatible_msrv)]
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.3.0"]
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::thread::sleep;
use std::time::Duration;
fn foo() {
let mut map: HashMap<&str, u32> = HashMap::new();
assert_eq!(map.entry("poneyland").key(), &"poneyland");
//~^ ERROR: is `1.3.0` but this item is stable since `1.10.0`
if let Entry::Vacant(v) = map.entry("poneyland") {
v.into_key();
//~^ ERROR: is `1.3.0` but this item is stable since `1.12.0`
}
// Should warn for `sleep` but not for `Duration` (which was added in `1.3.0`).
sleep(Duration::new(1, 0));
//~^ ERROR: is `1.3.0` but this item is stable since `1.4.0`
}
fn main() {}

View file

@ -0,0 +1,23 @@
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0`
--> $DIR/incompatible_msrv.rs:12:39
|
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
| ^^^^^
|
= note: `-D clippy::incompatible-msrv` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.12.0`
--> $DIR/incompatible_msrv.rs:15:11
|
LL | v.into_key();
| ^^^^^^^^^^
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0`
--> $DIR/incompatible_msrv.rs:19:5
|
LL | sleep(Duration::new(1, 0));
| ^^^^^
error: aborting due to 3 previous errors

View file

@ -0,0 +1,60 @@
#![warn(clippy::manual_c_str_literals)]
#![allow(clippy::no_effect)]
use std::ffi::CStr;
macro_rules! cstr {
($s:literal) => {
CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap()
};
}
macro_rules! macro_returns_c_str {
() => {
CStr::from_bytes_with_nul(b"foo\0").unwrap();
};
}
macro_rules! macro_returns_byte_string {
() => {
b"foo\0"
};
}
#[clippy::msrv = "1.76.0"]
fn pre_stabilization() {
CStr::from_bytes_with_nul(b"foo\0");
}
#[clippy::msrv = "1.77.0"]
fn post_stabilization() {
c"foo";
}
fn main() {
c"foo";
c"foo";
c"foo";
c"foo\\0sdsd";
CStr::from_bytes_with_nul(br"foo\\0sdsd\0").unwrap();
CStr::from_bytes_with_nul(br"foo\x00").unwrap();
CStr::from_bytes_with_nul(br##"foo#a\0"##).unwrap();
unsafe { c"foo" };
unsafe { c"foo" };
let _: *const _ = c"foo".as_ptr();
let _: *const _ = c"foo".as_ptr();
let _: *const _ = "foo".as_ptr(); // not a C-string
let _: *const _ = "".as_ptr();
let _: *const _ = c"foo".as_ptr().cast::<i8>();
let _ = "电脑".as_ptr();
let _ = "电脑\\".as_ptr();
let _ = c"电脑\\".as_ptr();
let _ = c"电脑".as_ptr();
let _ = c"电脑".as_ptr();
// Macro cases, don't lint:
cstr!("foo");
macro_returns_c_str!();
CStr::from_bytes_with_nul(macro_returns_byte_string!()).unwrap();
}

View file

@ -0,0 +1,60 @@
#![warn(clippy::manual_c_str_literals)]
#![allow(clippy::no_effect)]
use std::ffi::CStr;
macro_rules! cstr {
($s:literal) => {
CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap()
};
}
macro_rules! macro_returns_c_str {
() => {
CStr::from_bytes_with_nul(b"foo\0").unwrap();
};
}
macro_rules! macro_returns_byte_string {
() => {
b"foo\0"
};
}
#[clippy::msrv = "1.76.0"]
fn pre_stabilization() {
CStr::from_bytes_with_nul(b"foo\0");
}
#[clippy::msrv = "1.77.0"]
fn post_stabilization() {
CStr::from_bytes_with_nul(b"foo\0");
}
fn main() {
CStr::from_bytes_with_nul(b"foo\0");
CStr::from_bytes_with_nul(b"foo\x00");
CStr::from_bytes_with_nul(b"foo\0").unwrap();
CStr::from_bytes_with_nul(b"foo\\0sdsd\0").unwrap();
CStr::from_bytes_with_nul(br"foo\\0sdsd\0").unwrap();
CStr::from_bytes_with_nul(br"foo\x00").unwrap();
CStr::from_bytes_with_nul(br##"foo#a\0"##).unwrap();
unsafe { CStr::from_ptr(b"foo\0".as_ptr().cast()) };
unsafe { CStr::from_ptr(b"foo\0".as_ptr() as *const _) };
let _: *const _ = b"foo\0".as_ptr();
let _: *const _ = "foo\0".as_ptr();
let _: *const _ = "foo".as_ptr(); // not a C-string
let _: *const _ = "".as_ptr();
let _: *const _ = b"foo\0".as_ptr().cast::<i8>();
let _ = "电脑".as_ptr();
let _ = "电脑\\".as_ptr();
let _ = "电脑\\\0".as_ptr();
let _ = "电脑\0".as_ptr();
let _ = "电脑\x00".as_ptr();
// Macro cases, don't lint:
cstr!("foo");
macro_returns_c_str!();
CStr::from_bytes_with_nul(macro_returns_byte_string!()).unwrap();
}

View file

@ -0,0 +1,83 @@
error: calling `CStr::new` with a byte string literal
--> $DIR/manual_c_str_literals.rs:31:5
|
LL | CStr::from_bytes_with_nul(b"foo\0");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
|
= note: `-D clippy::manual-c-str-literals` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::manual_c_str_literals)]`
error: calling `CStr::new` with a byte string literal
--> $DIR/manual_c_str_literals.rs:35:5
|
LL | CStr::from_bytes_with_nul(b"foo\0");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: calling `CStr::new` with a byte string literal
--> $DIR/manual_c_str_literals.rs:36:5
|
LL | CStr::from_bytes_with_nul(b"foo\x00");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: calling `CStr::new` with a byte string literal
--> $DIR/manual_c_str_literals.rs:37:5
|
LL | CStr::from_bytes_with_nul(b"foo\0").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: calling `CStr::new` with a byte string literal
--> $DIR/manual_c_str_literals.rs:38:5
|
LL | CStr::from_bytes_with_nul(b"foo\\0sdsd\0").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo\\0sdsd"`
error: calling `CStr::from_ptr` with a byte string literal
--> $DIR/manual_c_str_literals.rs:43:14
|
LL | unsafe { CStr::from_ptr(b"foo\0".as_ptr().cast()) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: calling `CStr::from_ptr` with a byte string literal
--> $DIR/manual_c_str_literals.rs:44:14
|
LL | unsafe { CStr::from_ptr(b"foo\0".as_ptr() as *const _) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:45:23
|
LL | let _: *const _ = b"foo\0".as_ptr();
| ^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:46:23
|
LL | let _: *const _ = "foo\0".as_ptr();
| ^^^^^^^ help: use a `c""` literal: `c"foo"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:49:23
|
LL | let _: *const _ = b"foo\0".as_ptr().cast::<i8>();
| ^^^^^^^^ help: use a `c""` literal: `c"foo"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:52:13
|
LL | let _ = "电脑\\\0".as_ptr();
| ^^^^^^^^^^ help: use a `c""` literal: `c"电脑\\"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:53:13
|
LL | let _ = "电脑\0".as_ptr();
| ^^^^^^^^ help: use a `c""` literal: `c"电脑"`
error: manually constructing a nul-terminated string
--> $DIR/manual_c_str_literals.rs:54:13
|
LL | let _ = "电脑\x00".as_ptr();
| ^^^^^^^^^^ help: use a `c""` literal: `c"电脑"`
error: aborting due to 13 previous errors

View file

@ -14,6 +14,9 @@ fn main() {
_msrv_153(); _msrv_153();
_msrv_126(); _msrv_126();
_msrv_118(); _msrv_118();
issue_10393();
issue_12081();
} }
fn binary_heap_retain() { fn binary_heap_retain() {
@ -23,6 +26,11 @@ fn binary_heap_retain() {
binary_heap.retain(|x| x % 2 == 0); binary_heap.retain(|x| x % 2 == 0);
binary_heap.retain(|x| x % 2 == 0); binary_heap.retain(|x| x % 2 == 0);
// Do lint, because we use pattern matching
let mut tuples = BinaryHeap::from([(0, 1), (1, 2), (2, 3)]);
tuples.retain(|(ref x, ref y)| *x == 0);
tuples.retain(|(x, y)| *x == 0);
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
binary_heap = binary_heap binary_heap = binary_heap
.into_iter() .into_iter()
@ -55,6 +63,9 @@ fn btree_map_retain() {
btree_map.retain(|_, &mut v| v % 2 == 0); btree_map.retain(|_, &mut v| v % 2 == 0);
btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
// Do not lint, because the parameters are not matched in tuple pattern
btree_map = btree_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
// Do not lint. // Do not lint.
btree_map = btree_map btree_map = btree_map
.into_iter() .into_iter()
@ -76,6 +87,11 @@ fn btree_set_retain() {
btree_set.retain(|x| x % 2 == 0); btree_set.retain(|x| x % 2 == 0);
btree_set.retain(|x| x % 2 == 0); btree_set.retain(|x| x % 2 == 0);
// Do lint, because we use pattern matching
let mut tuples = BTreeSet::from([(0, 1), (1, 2), (2, 3)]);
tuples.retain(|(ref x, ref y)| *x == 0);
tuples.retain(|(x, y)| *x == 0);
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
btree_set = btree_set btree_set = btree_set
.iter() .iter()
@ -108,6 +124,9 @@ fn hash_map_retain() {
hash_map.retain(|_, &mut v| v % 2 == 0); hash_map.retain(|_, &mut v| v % 2 == 0);
hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0)); hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0));
// Do not lint, because the parameters are not matched in tuple pattern
hash_map = hash_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
// Do not lint. // Do not lint.
hash_map = hash_map hash_map = hash_map
.into_iter() .into_iter()
@ -128,6 +147,11 @@ fn hash_set_retain() {
hash_set.retain(|x| x % 2 == 0); hash_set.retain(|x| x % 2 == 0);
hash_set.retain(|x| x % 2 == 0); hash_set.retain(|x| x % 2 == 0);
// Do lint, because we use pattern matching
let mut tuples = HashSet::from([(0, 1), (1, 2), (2, 3)]);
tuples.retain(|(ref x, ref y)| *x == 0);
tuples.retain(|(x, y)| *x == 0);
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>(); hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>();
hash_set = hash_set hash_set = hash_set
@ -171,6 +195,11 @@ fn vec_retain() {
vec.retain(|x| x % 2 == 0); vec.retain(|x| x % 2 == 0);
vec.retain(|x| x % 2 == 0); vec.retain(|x| x % 2 == 0);
// Do lint, because we use pattern matching
let mut tuples = vec![(0, 1), (1, 2), (2, 3)];
tuples.retain(|(ref x, ref y)| *x == 0);
tuples.retain(|(x, y)| *x == 0);
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>(); vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>();
vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>(); vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>();
@ -246,3 +275,37 @@ fn _msrv_118() {
let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect(); let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect();
hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
} }
fn issue_10393() {
// Do lint
let mut vec = vec![(0, 1), (1, 2), (2, 3)];
vec.retain(|(x, y)| *x == 0);
// Do lint
let mut tuples = vec![(true, -2), (false, 3)];
tuples.retain(|(_, n)| *n > 0);
}
fn issue_11457() {
// Do not lint, as we need to modify the closure
let mut vals = vec![1, 2, 3, 4];
vals = vals.iter().filter(|v| **v != 1).cloned().collect();
// Do not lint, as we need to modify the closure
let mut s = String::from("foobar");
s = s.chars().filter(|c| *c != 'o').to_owned().collect();
}
fn issue_12081() {
let mut vec = vec![0, 1, 2];
// Do lint
vec.retain(|&x| x == 0);
vec.retain(|&x| x == 0);
vec.retain(|&x| x == 0);
// Do lint
vec.retain(|x| *x == 0);
vec.retain(|x| *x == 0);
vec.retain(|x| *x == 0);
}

View file

@ -14,6 +14,9 @@ fn main() {
_msrv_153(); _msrv_153();
_msrv_126(); _msrv_126();
_msrv_118(); _msrv_118();
issue_10393();
issue_12081();
} }
fn binary_heap_retain() { fn binary_heap_retain() {
@ -23,6 +26,11 @@ fn binary_heap_retain() {
binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect(); binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect();
binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect();
// Do lint, because we use pattern matching
let mut tuples = BinaryHeap::from([(0, 1), (1, 2), (2, 3)]);
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
binary_heap = binary_heap binary_heap = binary_heap
.into_iter() .into_iter()
@ -58,6 +66,9 @@ fn btree_map_retain() {
.filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0))
.collect(); .collect();
// Do not lint, because the parameters are not matched in tuple pattern
btree_map = btree_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
// Do not lint. // Do not lint.
btree_map = btree_map btree_map = btree_map
.into_iter() .into_iter()
@ -79,6 +90,11 @@ fn btree_set_retain() {
btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect();
// Do lint, because we use pattern matching
let mut tuples = BTreeSet::from([(0, 1), (1, 2), (2, 3)]);
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
btree_set = btree_set btree_set = btree_set
.iter() .iter()
@ -114,6 +130,9 @@ fn hash_map_retain() {
.filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0)) .filter(|(k, v)| (k % 2 == 0) && (v % 2 == 0))
.collect(); .collect();
// Do not lint, because the parameters are not matched in tuple pattern
hash_map = hash_map.into_iter().filter(|t| t.0 % 2 == 0).collect();
// Do not lint. // Do not lint.
hash_map = hash_map hash_map = hash_map
.into_iter() .into_iter()
@ -134,6 +153,11 @@ fn hash_set_retain() {
hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect();
hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
// Do lint, because we use pattern matching
let mut tuples = HashSet::from([(0, 1), (1, 2), (2, 3)]);
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>(); hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect::<HashSet<i8>>();
hash_set = hash_set hash_set = hash_set
@ -177,6 +201,11 @@ fn vec_retain() {
vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect();
vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
// Do lint, because we use pattern matching
let mut tuples = vec![(0, 1), (1, 2), (2, 3)];
tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
// Do not lint, because type conversion is performed // Do not lint, because type conversion is performed
vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>(); vec = vec.into_iter().filter(|x| x % 2 == 0).collect::<Vec<i8>>();
vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>(); vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect::<Vec<i8>>();
@ -252,3 +281,37 @@ fn _msrv_118() {
let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect(); let mut hash_map: HashMap<i8, i8> = (0..8).map(|x| (x, x * 10)).collect();
hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
} }
fn issue_10393() {
// Do lint
let mut vec = vec![(0, 1), (1, 2), (2, 3)];
vec = vec.into_iter().filter(|(x, y)| *x == 0).collect();
// Do lint
let mut tuples = vec![(true, -2), (false, 3)];
tuples = tuples.into_iter().filter(|(_, n)| *n > 0).collect();
}
fn issue_11457() {
// Do not lint, as we need to modify the closure
let mut vals = vec![1, 2, 3, 4];
vals = vals.iter().filter(|v| **v != 1).cloned().collect();
// Do not lint, as we need to modify the closure
let mut s = String::from("foobar");
s = s.chars().filter(|c| *c != 'o').to_owned().collect();
}
fn issue_12081() {
let mut vec = vec![0, 1, 2];
// Do lint
vec = vec.iter().filter(|&&x| x == 0).copied().collect();
vec = vec.iter().filter(|&&x| x == 0).cloned().collect();
vec = vec.into_iter().filter(|&x| x == 0).collect();
// Do lint
vec = vec.iter().filter(|&x| *x == 0).copied().collect();
vec = vec.iter().filter(|&x| *x == 0).cloned().collect();
vec = vec.into_iter().filter(|x| *x == 0).collect();
}

View file

@ -1,5 +1,5 @@
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:22:5 --> $DIR/manual_retain.rs:25:5
| |
LL | binary_heap = binary_heap.into_iter().filter(|x| x % 2 == 0).collect(); LL | binary_heap = binary_heap.into_iter().filter(|x| x % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
@ -8,31 +8,43 @@ LL | binary_heap = binary_heap.into_iter().filter(|x| x % 2 == 0).collect();
= help: to override `-D warnings` add `#[allow(clippy::manual_retain)]` = help: to override `-D warnings` add `#[allow(clippy::manual_retain)]`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:23:5 --> $DIR/manual_retain.rs:26:5
| |
LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect(); LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:24:5 --> $DIR/manual_retain.rs:27:5
| |
LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect(); LL | binary_heap = binary_heap.iter().filter(|&x| x % 2 == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `binary_heap.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:54:5 --> $DIR/manual_retain.rs:31:5
|
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:32:5
|
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:62:5
| |
LL | btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); LL | btree_map = btree_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|k, _| k % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|k, _| k % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:55:5 --> $DIR/manual_retain.rs:63:5
| |
LL | btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); LL | btree_map = btree_map.into_iter().filter(|(_, v)| v % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|_, &mut v| v % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_map.retain(|_, &mut v| v % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:56:5 --> $DIR/manual_retain.rs:64:5
| |
LL | / btree_map = btree_map LL | / btree_map = btree_map
LL | | .into_iter() LL | | .into_iter()
@ -41,37 +53,49 @@ LL | | .collect();
| |__________________^ help: consider calling `.retain()` instead: `btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` | |__________________^ help: consider calling `.retain()` instead: `btree_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:78:5 --> $DIR/manual_retain.rs:89:5
| |
LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect(); LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:79:5 --> $DIR/manual_retain.rs:90:5
| |
LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); LL | btree_set = btree_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:80:5 --> $DIR/manual_retain.rs:91:5
| |
LL | btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect(); LL | btree_set = btree_set.into_iter().filter(|x| x % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `btree_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:110:5 --> $DIR/manual_retain.rs:95:5
|
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:96:5
|
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:126:5
| |
LL | hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect(); LL | hash_map = hash_map.into_iter().filter(|(k, _)| k % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|k, _| k % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|k, _| k % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:111:5 --> $DIR/manual_retain.rs:127:5
| |
LL | hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect(); LL | hash_map = hash_map.into_iter().filter(|(_, v)| v % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|_, &mut v| v % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_map.retain(|_, &mut v| v % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:112:5 --> $DIR/manual_retain.rs:128:5
| |
LL | / hash_map = hash_map LL | / hash_map = hash_map
LL | | .into_iter() LL | | .into_iter()
@ -80,64 +104,136 @@ LL | | .collect();
| |__________________^ help: consider calling `.retain()` instead: `hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))` | |__________________^ help: consider calling `.retain()` instead: `hash_map.retain(|k, &mut v| (k % 2 == 0) && (v % 2 == 0))`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:133:5 --> $DIR/manual_retain.rs:152:5
| |
LL | hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect(); LL | hash_set = hash_set.into_iter().filter(|x| x % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:134:5 --> $DIR/manual_retain.rs:153:5
| |
LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect(); LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:135:5 --> $DIR/manual_retain.rs:154:5
| |
LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect(); LL | hash_set = hash_set.iter().filter(|&x| x % 2 == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `hash_set.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:164:5 --> $DIR/manual_retain.rs:158:5
|
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:159:5
|
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:188:5
| |
LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect(); LL | s = s.chars().filter(|&c| c != 'o').to_owned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `s.retain(|c| c != 'o')` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `s.retain(|c| c != 'o')`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:176:5 --> $DIR/manual_retain.rs:200:5
| |
LL | vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect(); LL | vec = vec.iter().filter(|&x| x % 2 == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:177:5 --> $DIR/manual_retain.rs:201:5
| |
LL | vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect(); LL | vec = vec.iter().filter(|&x| x % 2 == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:178:5 --> $DIR/manual_retain.rs:202:5
| |
LL | vec = vec.into_iter().filter(|x| x % 2 == 0).collect(); LL | vec = vec.into_iter().filter(|x| x % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:200:5 --> $DIR/manual_retain.rs:206:5
|
LL | tuples = tuples.iter().filter(|(ref x, ref y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(ref x, ref y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:207:5
|
LL | tuples = tuples.iter().filter(|(x, y)| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(x, y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:229:5
| |
LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect(); LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:201:5 --> $DIR/manual_retain.rs:230:5
| |
LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect(); LL | vec_deque = vec_deque.iter().filter(|&x| x % 2 == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
error: this expression can be written more simply using `.retain()` error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:202:5 --> $DIR/manual_retain.rs:231:5
| |
LL | vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect(); LL | vec_deque = vec_deque.into_iter().filter(|x| x % 2 == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec_deque.retain(|x| x % 2 == 0)`
error: aborting due to 22 previous errors error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:288:5
|
LL | vec = vec.into_iter().filter(|(x, y)| *x == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|(x, y)| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:292:5
|
LL | tuples = tuples.into_iter().filter(|(_, n)| *n > 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `tuples.retain(|(_, n)| *n > 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:309:5
|
LL | vec = vec.iter().filter(|&&x| x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:310:5
|
LL | vec = vec.iter().filter(|&&x| x == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:311:5
|
LL | vec = vec.into_iter().filter(|&x| x == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|&x| x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:314:5
|
LL | vec = vec.iter().filter(|&x| *x == 0).copied().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:315:5
|
LL | vec = vec.iter().filter(|&x| *x == 0).cloned().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
error: this expression can be written more simply using `.retain()`
--> $DIR/manual_retain.rs:316:5
|
LL | vec = vec.into_iter().filter(|x| *x == 0).collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling `.retain()` instead: `vec.retain(|x| *x == 0)`
error: aborting due to 38 previous errors

View file

@ -114,4 +114,12 @@ fn main() {
a_usize % b_usize; a_usize % b_usize;
let mut a_usize: usize = 1; let mut a_usize: usize = 1;
a_usize %= 2; a_usize %= 2;
// No lint when comparing to zero
let a = -1;
let mut b = 2;
let c = a % b == 0;
let c = 0 == a % b;
let c = a % b != 0;
let c = 0 != a % b;
} }

View file

@ -131,6 +131,9 @@ fn main() {
0 0
} }
} }
// issue #11786
let x: (&str,) = ("",);
} }
#[allow(clippy::needless_borrowed_reference)] #[allow(clippy::needless_borrowed_reference)]

View file

@ -131,6 +131,9 @@ fn main() {
0 0
} }
} }
// issue #11786
let x: (&str,) = (&"",);
} }
#[allow(clippy::needless_borrowed_reference)] #[allow(clippy::needless_borrowed_reference)]

View file

@ -121,41 +121,47 @@ error: this expression creates a reference which is immediately dereferenced by
LL | (&&5).foo(); LL | (&&5).foo();
| ^^^^^ help: change this to: `(&5)` | ^^^^^ help: change this to: `(&5)`
error: this expression creates a reference which is immediately dereferenced by the compiler
--> $DIR/needless_borrow.rs:136:23
|
LL | let x: (&str,) = (&"",);
| ^^^ help: change this to: `""`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:175:13 --> $DIR/needless_borrow.rs:178:13
| |
LL | (&self.f)() LL | (&self.f)()
| ^^^^^^^^^ help: change this to: `(self.f)` | ^^^^^^^^^ help: change this to: `(self.f)`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:184:13 --> $DIR/needless_borrow.rs:187:13
| |
LL | (&mut self.f)() LL | (&mut self.f)()
| ^^^^^^^^^^^^^ help: change this to: `(self.f)` | ^^^^^^^^^^^^^ help: change this to: `(self.f)`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:221:22 --> $DIR/needless_borrow.rs:224:22
| |
LL | let _ = &mut (&mut { x.u }).x; LL | let _ = &mut (&mut { x.u }).x;
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }` | ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:228:22 --> $DIR/needless_borrow.rs:231:22
| |
LL | let _ = &mut (&mut { x.u }).x; LL | let _ = &mut (&mut { x.u }).x;
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }` | ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:232:22 --> $DIR/needless_borrow.rs:235:22
| |
LL | let _ = &mut (&mut x.u).x; LL | let _ = &mut (&mut x.u).x;
| ^^^^^^^^^^ help: change this to: `x.u` | ^^^^^^^^^^ help: change this to: `x.u`
error: this expression borrows a value the compiler would automatically borrow error: this expression borrows a value the compiler would automatically borrow
--> $DIR/needless_borrow.rs:233:22 --> $DIR/needless_borrow.rs:236:22
| |
LL | let _ = &mut (&mut { x.u }).x; LL | let _ = &mut (&mut { x.u }).x;
| ^^^^^^^^^^^^^^ help: change this to: `{ x.u }` | ^^^^^^^^^^^^^^ help: change this to: `{ x.u }`
error: aborting due to 26 previous errors error: aborting due to 27 previous errors

View file

@ -77,3 +77,53 @@ fn issue11616() -> Result<(), ()> {
}; };
Ok(()) Ok(())
} }
fn issue11982() {
mod bar {
pub struct Error;
pub fn foo(_: bool) -> Result<(), Error> {
Ok(())
}
}
pub struct Error;
impl From<bar::Error> for Error {
fn from(_: bar::Error) -> Self {
Error
}
}
fn foo(ok: bool) -> Result<(), Error> {
if !ok {
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
};
Ok(())
}
}
fn issue11982_no_conversion() {
mod bar {
pub struct Error;
pub fn foo(_: bool) -> Result<(), Error> {
Ok(())
}
}
fn foo(ok: bool) -> Result<(), bar::Error> {
if !ok {
return bar::foo(ok).map(|_| Ok::<(), bar::Error>(()))?;
};
Ok(())
}
}
fn general_return() {
fn foo(ok: bool) -> Result<(), ()> {
let bar = Result::Ok(Result::<(), ()>::Ok(()));
if !ok {
return bar?;
};
Ok(())
}
}

View file

@ -77,3 +77,53 @@ fn issue11616() -> Result<(), ()> {
}; };
Ok(()) Ok(())
} }
fn issue11982() {
mod bar {
pub struct Error;
pub fn foo(_: bool) -> Result<(), Error> {
Ok(())
}
}
pub struct Error;
impl From<bar::Error> for Error {
fn from(_: bar::Error) -> Self {
Error
}
}
fn foo(ok: bool) -> Result<(), Error> {
if !ok {
return bar::foo(ok).map(|_| Ok::<(), Error>(()))?;
};
Ok(())
}
}
fn issue11982_no_conversion() {
mod bar {
pub struct Error;
pub fn foo(_: bool) -> Result<(), Error> {
Ok(())
}
}
fn foo(ok: bool) -> Result<(), bar::Error> {
if !ok {
return bar::foo(ok).map(|_| Ok::<(), bar::Error>(()))?;
};
Ok(())
}
}
fn general_return() {
fn foo(ok: bool) -> Result<(), ()> {
let bar = Result::Ok(Result::<(), ()>::Ok(()));
if !ok {
return bar?;
};
Ok(())
}
}

View file

@ -1,4 +1,4 @@
#![feature(inline_const)] #![feature(inline_const, try_blocks)]
#![allow( #![allow(
clippy::eq_op, clippy::eq_op,
clippy::single_match, clippy::single_match,
@ -400,6 +400,15 @@ pub fn test32() {
} }
} }
pub fn issue12205() -> Option<()> {
loop {
let _: Option<_> = try {
None?;
return Some(());
};
}
}
fn main() { fn main() {
test1(); test1();
test2(); test2();

View file

@ -145,3 +145,14 @@ fn issue10836() {
// Should not lint // Should not lint
let _: bool = !!Foo(true); let _: bool = !!Foo(true);
} }
fn issue11932() {
let x: i32 = unimplemented!();
#[allow(clippy::nonminimal_bool)]
let _ = x % 2 == 0 || {
// Should not lint
assert!(x > 0);
x % 3 == 0
};
}

View file

@ -102,3 +102,12 @@ mod issue11707 {
fn avoid_double_parens() { fn avoid_double_parens() {
std::convert::identity(13_i32 + 36_i32).leading_zeros(); std::convert::identity(13_i32 + 36_i32).leading_zeros();
} }
fn fp_11274() {
macro_rules! m {
($closure:expr) => {
$closure(1)
};
}
m!(|x| println!("{x}"));
}

View file

@ -102,3 +102,12 @@ mod issue11707 {
fn avoid_double_parens() { fn avoid_double_parens() {
std::convert::identity((|| 13_i32 + 36_i32)()).leading_zeros(); std::convert::identity((|| 13_i32 + 36_i32)()).leading_zeros();
} }
fn fp_11274() {
macro_rules! m {
($closure:expr) => {
$closure(1)
};
}
m!(|x| println!("{x}"));
}

View file

@ -1,6 +1,7 @@
//@aux-build:proc_macros.rs //@aux-build:proc_macros.rs
#![allow(unused, clippy::no_effect, clippy::needless_pass_by_ref_mut)] #![allow(unused, clippy::no_effect, clippy::needless_pass_by_ref_mut)]
#![warn(clippy::redundant_locals)] #![warn(clippy::redundant_locals)]
#![feature(async_closure, coroutines)]
extern crate proc_macros; extern crate proc_macros;
use proc_macros::{external, with_span}; use proc_macros::{external, with_span};
@ -163,3 +164,48 @@ fn drop_compose() {
let b = ComposeDrop { d: WithDrop(1) }; let b = ComposeDrop { d: WithDrop(1) };
let a = a; let a = a;
} }
fn issue12225() {
fn assert_static<T: 'static>(_: T) {}
let v1 = String::new();
let v2 = String::new();
let v3 = String::new();
let v4 = String::new();
let v5 = String::new();
let v6 = String::new();
assert_static(|| {
let v1 = v1;
dbg!(&v1);
});
assert_static(async {
let v2 = v2;
dbg!(&v2);
});
assert_static(|| async {
let v3 = v3;
dbg!(&v3);
});
assert_static(async || {
let v4 = v4;
dbg!(&v4);
});
assert_static(static || {
let v5 = v5;
yield;
});
assert_static(|| {
let v6 = v6;
yield;
});
fn foo(a: &str, b: &str) {}
let do_not_move = String::new();
let things_to_move = vec!["a".to_string(), "b".to_string()];
let futures = things_to_move.into_iter().map(|move_me| async {
let move_me = move_me;
foo(&do_not_move, &move_me)
});
}

Some files were not shown because too many files have changed in this diff Show more