Merge commit '2b030eb03d' into clippyup

This commit is contained in:
Philipp Krones 2023-10-21 14:16:11 +02:00
parent d9259fdedd
commit 8e7d1678c4
125 changed files with 2315 additions and 844 deletions

View file

@ -5463,6 +5463,7 @@ Released 2018-09-13
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string [`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings [`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools [`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools
[`struct_field_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_field_names
[`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter [`stutter`]: https://rust-lang.github.io/rust-clippy/master/index.html#stutter
[`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops [`suboptimal_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#suboptimal_flops
[`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl [`suspicious_arithmetic_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
@ -5625,6 +5626,7 @@ Released 2018-09-13
[`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold [`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold
[`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack [`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack
[`enum-variant-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-name-threshold [`enum-variant-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-name-threshold
[`struct-field-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#struct-field-name-threshold
[`enum-variant-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-size-threshold [`enum-variant-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#enum-variant-size-threshold
[`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold [`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold
[`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold [`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold

View file

@ -29,7 +29,7 @@ color-print = "0.3.4" # Sync version with Cargo
anstream = "0.5.0" anstream = "0.5.0"
[dev-dependencies] [dev-dependencies]
ui_test = "0.20" ui_test = "0.21.2"
tester = "0.9" tester = "0.9"
regex = "1.5" regex = "1.5"
toml = "0.7.3" toml = "0.7.3"

View file

@ -30,6 +30,7 @@ because that's clearly a non-descriptive name.
- [Documentation](#documentation) - [Documentation](#documentation)
- [Running rustfmt](#running-rustfmt) - [Running rustfmt](#running-rustfmt)
- [Debugging](#debugging) - [Debugging](#debugging)
- [Conflicting lints](#conflicting-lints)
- [PR Checklist](#pr-checklist) - [PR Checklist](#pr-checklist)
- [Adding configuration to a lint](#adding-configuration-to-a-lint) - [Adding configuration to a lint](#adding-configuration-to-a-lint)
- [Cheat Sheet](#cheat-sheet) - [Cheat Sheet](#cheat-sheet)
@ -612,6 +613,24 @@ output in the `stdout` part.
[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html [`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html
## Conflicting lints
There are several lints that deal with the same pattern but suggest different approaches. In other words, some lints
may suggest modifications that go in the opposite direction to what some other lints already propose for the same
code, creating conflicting diagnostics.
When you are creating a lint that ends up in this scenario, the following tips should be encouraged to guide
classification:
* The only case where they should be in the same category is if that category is `restriction`. For example,
`semicolon_inside_block` and `semicolon_outside_block`.
* For all the other cases, they should be in different categories with different levels of allowance. For example,
`implicit_return` (restriction, allow) and `needless_return` (style, warn).
For lints that are in different categories, it is also recommended that at least one of them should be in the
`restriction` category. The reason for this is that the `restriction` group is the only group where we don't
recommend to enable the entire set, but cherry pick lints out of.
## PR Checklist ## PR Checklist
Before submitting your PR make sure you followed all the basic requirements: Before submitting your PR make sure you followed all the basic requirements:

View file

@ -100,7 +100,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
## `msrv` ## `msrv`
The minimum rust version that the project supports The minimum rust version that the project supports
**Default Value:** `None` (`Option<String>`) **Default Value:** `Msrv { stack: [] }` (`crate::Msrv`)
--- ---
**Affected lints:** **Affected lints:**
@ -273,6 +273,16 @@ The minimum number of enum variants for the lints about variant names to trigger
* [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names) * [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names)
## `struct-field-name-threshold`
The minimum number of struct fields for the lints about field names to trigger
**Default Value:** `3` (`u64`)
---
**Affected lints:**
* [`struct_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#struct_variant_names)
## `enum-variant-size-threshold` ## `enum-variant-size-threshold`
The maximum size of an enum's variant to avoid box suggestion The maximum size of an enum's variant to avoid box suggestion

View file

@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{AsyncCoroutineKind, Body, BodyId, ExprKind, CoroutineKind, QPath}; use rustc_hir::{AsyncCoroutineKind, Body, BodyId, CoroutineKind, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};

View file

@ -287,5 +287,8 @@ fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
} }
fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool { fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
matches!(cx.tcx.get_diagnostic_name(def_id), Some(sym::RefCellRef | sym::RefCellRefMut)) matches!(
cx.tcx.get_diagnostic_name(def_id),
Some(sym::RefCellRef | sym::RefCellRefMut)
)
} }

View file

@ -3,8 +3,9 @@ use clippy_utils::macros::macro_backtrace;
use clippy_utils::ty::expr_sig; use clippy_utils::ty::expr_sig;
use clippy_utils::{get_parent_node, is_default_equivalent, path_def_id}; use clippy_utils::{get_parent_node, is_default_equivalent, path_def_id};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::{def::Res, Block, Expr, ExprKind, Local, Node, QPath, TyKind}; use rustc_hir::{Block, Expr, ExprKind, Local, Node, QPath, TyKind};
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::print::with_forced_trimmed_paths; use rustc_middle::ty::print::with_forced_trimmed_paths;

View file

@ -154,9 +154,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO, crate::endian_bytes::LITTLE_ENDIAN_BYTES_INFO,
crate::entry::MAP_ENTRY_INFO, crate::entry::MAP_ENTRY_INFO,
crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO, crate::enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT_INFO,
crate::enum_variants::ENUM_VARIANT_NAMES_INFO,
crate::enum_variants::MODULE_INCEPTION_INFO,
crate::enum_variants::MODULE_NAME_REPETITIONS_INFO,
crate::equatable_if_let::EQUATABLE_IF_LET_INFO, crate::equatable_if_let::EQUATABLE_IF_LET_INFO,
crate::error_impl_error::ERROR_IMPL_ERROR_INFO, crate::error_impl_error::ERROR_IMPL_ERROR_INFO,
crate::escape::BOXED_LOCAL_INFO, crate::escape::BOXED_LOCAL_INFO,
@ -226,6 +223,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO, crate::instant_subtraction::UNCHECKED_DURATION_SUBTRACTION_INFO,
crate::int_plus_one::INT_PLUS_ONE_INFO, crate::int_plus_one::INT_PLUS_ONE_INFO,
crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO, crate::invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS_INFO,
crate::item_name_repetitions::ENUM_VARIANT_NAMES_INFO,
crate::item_name_repetitions::MODULE_INCEPTION_INFO,
crate::item_name_repetitions::MODULE_NAME_REPETITIONS_INFO,
crate::item_name_repetitions::STRUCT_FIELD_NAMES_INFO,
crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO, crate::items_after_statements::ITEMS_AFTER_STATEMENTS_INFO,
crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO, crate::items_after_test_module::ITEMS_AFTER_TEST_MODULE_INFO,
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO, crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_context;
use clippy_utils::last_path_segment; use clippy_utils::last_path_segment;
use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind}; use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint; use clippy_utils::diagnostics::span_lint;
use clippy_utils::{is_entrypoint_fn}; use clippy_utils::is_entrypoint_fn;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::is_c_void;
use clippy_utils::path_def_id; use clippy_utils::path_def_id;
use clippy_utils::ty::is_c_void;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, QPath}; use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -1,50 +1,104 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_in_test_function; use clippy_utils::is_in_test_function;
use rustc_hir as hir;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, HirId}; use rustc_hir::{Body, GenericParam, Generics, HirId, ImplItem, ImplItemKind, TraitItem, TraitItemKind};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_span::Span; use rustc_span::symbol::Ident;
use rustc_span::{BytePos, Span};
use super::IMPL_TRAIT_IN_PARAMS; use super::IMPL_TRAIT_IN_PARAMS;
fn report(
cx: &LateContext<'_>,
param: &GenericParam<'_>,
ident: &Ident,
generics: &Generics<'_>,
first_param_span: Span,
) {
// No generics with nested generics, and no generics like FnMut(x)
span_lint_and_then(
cx,
IMPL_TRAIT_IN_PARAMS,
param.span,
"`impl Trait` used as a function parameter",
|diag| {
if let Some(gen_span) = generics.span_for_param_suggestion() {
// If there's already a generic param with the same bound, do not lint **this** suggestion.
diag.span_suggestion_with_style(
gen_span,
"add a type parameter",
format!(", {{ /* Generic name */ }}: {}", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders,
rustc_errors::SuggestionStyle::ShowAlways,
);
} else {
diag.span_suggestion_with_style(
Span::new(
first_param_span.lo() - rustc_span::BytePos(1),
ident.span.hi(),
ident.span.ctxt(),
ident.span.parent(),
),
"add a type parameter",
format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders,
rustc_errors::SuggestionStyle::ShowAlways,
);
}
},
);
}
pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) { pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) {
if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() && !is_in_test_function(cx.tcx, hir_id) if_chain! {
{ if let FnKind::ItemFn(ident, generics, _) = kind;
if let FnKind::ItemFn(ident, generics, _) = kind { if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
if !is_in_test_function(cx.tcx, hir_id);
then {
for param in generics.params { for param in generics.params {
if param.is_impl_trait() { if param.is_impl_trait() {
// No generics with nested generics, and no generics like FnMut(x) report(cx, param, ident, generics, body.params[0].span);
span_lint_and_then( };
cx, }
IMPL_TRAIT_IN_PARAMS, }
param.span, }
"'`impl Trait` used as a function parameter'", }
|diag| {
if let Some(gen_span) = generics.span_for_param_suggestion() { pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
diag.span_suggestion_with_style( if_chain! {
gen_span, if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
"add a type parameter", if let hir::Node::Item(item) = cx.tcx.hir().get_parent(impl_item.hir_id());
format!(", {{ /* Generic name */ }}: {}", &param.name.ident().as_str()[5..]), if let hir::ItemKind::Impl(impl_) = item.kind;
rustc_errors::Applicability::HasPlaceholders, if let hir::Impl { of_trait, .. } = *impl_;
rustc_errors::SuggestionStyle::ShowAlways, if of_trait.is_none();
); let body = cx.tcx.hir().body(body_id);
} else { if cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public();
diag.span_suggestion_with_style( if !is_in_test_function(cx.tcx, impl_item.hir_id());
Span::new( then {
body.params[0].span.lo() - rustc_span::BytePos(1), for param in impl_item.generics.params {
ident.span.hi(), if param.is_impl_trait() {
ident.span.ctxt(), report(cx, param, &impl_item.ident, impl_item.generics, body.params[0].span);
ident.span.parent(), }
), }
"add a type parameter", }
format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]), }
rustc_errors::Applicability::HasPlaceholders, }
rustc_errors::SuggestionStyle::ShowAlways,
); pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
} if_chain! {
}, if !avoid_breaking_exported_api;
); if let TraitItemKind::Fn(_, _) = trait_item.kind;
if let hir::Node::Item(item) = cx.tcx.hir().get_parent(trait_item.hir_id());
// ^^ (Will always be a trait)
if !item.vis_span.is_empty(); // Is public
if !is_in_test_function(cx.tcx, trait_item.hir_id());
then {
for param in trait_item.generics.params {
if param.is_impl_trait() {
let sp = trait_item.ident.span.with_hi(trait_item.ident.span.hi() + BytePos(1));
report(cx, param, &trait_item.ident, trait_item.generics, sp.shrink_to_hi());
} }
} }
} }

View file

@ -360,18 +360,26 @@ declare_clippy_lint! {
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
#[allow(clippy::struct_field_names)]
pub struct Functions { pub struct Functions {
too_many_arguments_threshold: u64, too_many_arguments_threshold: u64,
too_many_lines_threshold: u64, too_many_lines_threshold: u64,
large_error_threshold: u64, large_error_threshold: u64,
avoid_breaking_exported_api: bool,
} }
impl Functions { impl Functions {
pub fn new(too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64) -> Self { pub fn new(
too_many_arguments_threshold: u64,
too_many_lines_threshold: u64,
large_error_threshold: u64,
avoid_breaking_exported_api: bool,
) -> Self {
Self { Self {
too_many_arguments_threshold, too_many_arguments_threshold,
too_many_lines_threshold, too_many_lines_threshold,
large_error_threshold, large_error_threshold,
avoid_breaking_exported_api,
} }
} }
} }
@ -415,6 +423,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
must_use::check_impl_item(cx, item); must_use::check_impl_item(cx, item);
result::check_impl_item(cx, item, self.large_error_threshold); result::check_impl_item(cx, item, self.large_error_threshold);
impl_trait_in_params::check_impl_item(cx, item);
} }
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
@ -422,5 +431,6 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
not_unsafe_ptr_arg_deref::check_trait_item(cx, item); not_unsafe_ptr_arg_deref::check_trait_item(cx, item);
must_use::check_trait_item(cx, item); must_use::check_trait_item(cx, item);
result::check_trait_item(cx, item, self.large_error_threshold); result::check_trait_item(cx, item, self.large_error_threshold);
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
} }
} }

View file

@ -1,9 +1,10 @@
//! lint on enum variants that are prefixed or suffixed by the same characters //! lint on enum variants that are prefixed or suffixed by the same characters
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_hir};
use clippy_utils::macros::span_is_local;
use clippy_utils::source::is_present_in_source; use clippy_utils::source::is_present_in_source;
use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start}; use clippy_utils::str_utils::{camel_case_split, count_match_end, count_match_start, to_camel_case, to_snake_case};
use rustc_hir::{EnumDef, Item, ItemKind, OwnerId, Variant}; use rustc_hir::{EnumDef, FieldDef, Item, ItemKind, OwnerId, Variant, VariantData};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span; use rustc_span::source_map::Span;
@ -103,32 +104,184 @@ declare_clippy_lint! {
style, style,
"modules that have the same name as their parent module" "modules that have the same name as their parent module"
} }
declare_clippy_lint! {
/// ### What it does
/// Detects struct fields that are prefixed or suffixed
/// by the same characters or the name of the struct itself.
///
/// ### Why is this bad?
/// Information common to all struct fields is better represented in the struct name.
///
/// ### Limitations
/// Characters with no casing will be considered when comparing prefixes/suffixes
/// This applies to numbers and non-ascii characters without casing
/// e.g. `foo1` and `foo2` is considered to have different prefixes
/// (the prefixes are `foo1` and `foo2` respectively), as also `bar螃`, `bar蟹`
///
/// ### Example
/// ```rust
/// struct Cake {
/// cake_sugar: u8,
/// cake_flour: u8,
/// cake_eggs: u8
/// }
/// ```
/// Use instead:
/// ```rust
/// struct Cake {
/// sugar: u8,
/// flour: u8,
/// eggs: u8
/// }
/// ```
#[clippy::version = "1.75.0"]
pub STRUCT_FIELD_NAMES,
pedantic,
"structs where all fields share a prefix/postfix or contain the name of the struct"
}
pub struct EnumVariantNames { pub struct ItemNameRepetitions {
modules: Vec<(Symbol, String, OwnerId)>, modules: Vec<(Symbol, String, OwnerId)>,
threshold: u64, enum_threshold: u64,
struct_threshold: u64,
avoid_breaking_exported_api: bool, avoid_breaking_exported_api: bool,
allow_private_module_inception: bool, allow_private_module_inception: bool,
} }
impl EnumVariantNames { impl ItemNameRepetitions {
#[must_use] #[must_use]
pub fn new(threshold: u64, avoid_breaking_exported_api: bool, allow_private_module_inception: bool) -> Self { pub fn new(
enum_threshold: u64,
struct_threshold: u64,
avoid_breaking_exported_api: bool,
allow_private_module_inception: bool,
) -> Self {
Self { Self {
modules: Vec::new(), modules: Vec::new(),
threshold, enum_threshold,
struct_threshold,
avoid_breaking_exported_api, avoid_breaking_exported_api,
allow_private_module_inception, allow_private_module_inception,
} }
} }
} }
impl_lint_pass!(EnumVariantNames => [ impl_lint_pass!(ItemNameRepetitions => [
ENUM_VARIANT_NAMES, ENUM_VARIANT_NAMES,
STRUCT_FIELD_NAMES,
MODULE_NAME_REPETITIONS, MODULE_NAME_REPETITIONS,
MODULE_INCEPTION MODULE_INCEPTION
]); ]);
#[must_use]
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
prefixes.iter().all(|p| p == &"" || p == &"_")
}
fn check_fields(cx: &LateContext<'_>, threshold: u64, item: &Item<'_>, fields: &[FieldDef<'_>]) {
if (fields.len() as u64) < threshold {
return;
}
check_struct_name_repetition(cx, item, fields);
// if the SyntaxContext of the identifiers of the fields and struct differ dont lint them.
// this prevents linting in macros in which the location of the field identifier names differ
if !fields.iter().all(|field| item.ident.span.eq_ctxt(field.ident.span)) {
return;
}
let mut pre: Vec<&str> = match fields.first() {
Some(first_field) => first_field.ident.name.as_str().split('_').collect(),
None => return,
};
let mut post = pre.clone();
post.reverse();
for field in fields {
let field_split: Vec<&str> = field.ident.name.as_str().split('_').collect();
if field_split.len() == 1 {
return;
}
pre = pre
.into_iter()
.zip(field_split.iter())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
post = post
.into_iter()
.zip(field_split.iter().rev())
.take_while(|(a, b)| &a == b)
.map(|e| e.0)
.collect();
}
let prefix = pre.join("_");
post.reverse();
let postfix = match post.last() {
Some(&"") => post.join("_") + "_",
Some(_) | None => post.join("_"),
};
if fields.len() > 1 {
let (what, value) = match (
prefix.is_empty() || prefix.chars().all(|c| c == '_'),
postfix.is_empty(),
) {
(true, true) => return,
(false, _) => ("pre", prefix),
(true, false) => ("post", postfix),
};
span_lint_and_help(
cx,
STRUCT_FIELD_NAMES,
item.span,
&format!("all fields have the same {what}fix: `{value}`"),
None,
&format!("remove the {what}fixes"),
);
}
}
fn check_struct_name_repetition(cx: &LateContext<'_>, item: &Item<'_>, fields: &[FieldDef<'_>]) {
let snake_name = to_snake_case(item.ident.name.as_str());
let item_name_words: Vec<&str> = snake_name.split('_').collect();
for field in fields {
if field.ident.span.eq_ctxt(item.ident.span) {
//consider linting only if the field identifier has the same SyntaxContext as the item(struct)
let field_words: Vec<&str> = field.ident.name.as_str().split('_').collect();
if field_words.len() >= item_name_words.len() {
// if the field name is shorter than the struct name it cannot contain it
if field_words.iter().zip(item_name_words.iter()).all(|(a, b)| a == b) {
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name starts with the struct's name",
);
}
if field_words.len() > item_name_words.len() {
// lint only if the end is not covered by the start
if field_words
.iter()
.rev()
.zip(item_name_words.iter().rev())
.all(|(a, b)| a == b)
{
span_lint_hir(
cx,
STRUCT_FIELD_NAMES,
field.hir_id,
field.span,
"field name ends with the struct's name",
);
}
}
}
}
}
}
fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) { fn check_enum_start(cx: &LateContext<'_>, item_name: &str, variant: &Variant<'_>) {
let name = variant.ident.name.as_str(); let name = variant.ident.name.as_str();
let item_name_chars = item_name.chars().count(); let item_name_chars = item_name.chars().count();
@ -218,35 +371,7 @@ fn check_variant(cx: &LateContext<'_>, threshold: u64, def: &EnumDef<'_>, item_n
); );
} }
#[must_use] impl LateLintPass<'_> for ItemNameRepetitions {
fn have_no_extra_prefix(prefixes: &[&str]) -> bool {
prefixes.iter().all(|p| p == &"" || p == &"_")
}
#[must_use]
fn to_camel_case(item_name: &str) -> String {
let mut s = String::new();
let mut up = true;
for c in item_name.chars() {
if c.is_uppercase() {
// we only turn snake case text into CamelCase
return item_name.to_string();
}
if c == '_' {
up = true;
continue;
}
if up {
up = false;
s.extend(c.to_uppercase());
} else {
s.push(c);
}
}
s
}
impl LateLintPass<'_> for EnumVariantNames {
fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) { fn check_item_post(&mut self, _cx: &LateContext<'_>, _item: &Item<'_>) {
let last = self.modules.pop(); let last = self.modules.pop();
assert!(last.is_some()); assert!(last.is_some());
@ -303,9 +428,15 @@ impl LateLintPass<'_> for EnumVariantNames {
} }
} }
} }
if let ItemKind::Enum(ref def, _) = item.kind { if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id))
if !(self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id)) { && span_is_local(item.span)
check_variant(cx, self.threshold, def, item_name, item.span); {
match item.kind {
ItemKind::Enum(def, _) => check_variant(cx, self.enum_threshold, &def, item_name, item.span),
ItemKind::Struct(VariantData::Struct(fields, _), _) => {
check_fields(cx, self.struct_threshold, item, fields);
},
_ => (),
} }
} }
self.modules.push((item.ident.name, item_camel, item.owner_id)); self.modules.push((item.ident.name, item_camel, item.owner_id));

View file

@ -9,6 +9,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty}; use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, Symbol}; use rustc_span::{sym, Symbol};
use std::iter;
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -52,12 +53,13 @@ declare_clippy_lint! {
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// This is the opposite of the `iter_without_into_iter` lint. /// This is the opposite of the `iter_without_into_iter` lint.
/// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method. /// It looks for `IntoIterator for (&|&mut) Type` implementations without an inherent `iter` or `iter_mut` method
/// on the type or on any of the types in its `Deref` chain.
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// It's not bad, but having them is idiomatic and allows the type to be used in iterator chains /// It's not bad, but having them is idiomatic and allows the type to be used in iterator chains
/// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).iter()` syntax /// by just calling `.iter()`, instead of the more awkward `<&Type>::into_iter` or `(&val).into_iter()` syntax
/// in case of ambiguity with another `Intoiterator` impl. /// in case of ambiguity with another `IntoIterator` impl.
/// ///
/// ### Example /// ### Example
/// ```rust /// ```rust
@ -102,7 +104,20 @@ fn is_nameable_in_impl_trait(ty: &rustc_hir::Ty<'_>) -> bool {
!matches!(ty.kind, TyKind::OpaqueDef(..)) !matches!(ty.kind, TyKind::OpaqueDef(..))
} }
fn type_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool { /// Returns the deref chain of a type, starting with the type itself.
fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl Iterator<Item = Ty<'tcx>> + 'cx {
iter::successors(Some(ty), |&ty| {
if let Some(deref_did) = cx.tcx.lang_items().deref_trait()
&& implements_trait(cx, ty, deref_did, &[])
{
make_normalized_projection(cx.tcx, cx.param_env, deref_did, sym::Target, [ty])
} else {
None
}
})
}
fn adt_has_inherent_method(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) { if let Some(ty_did) = ty.ty_adt_def().map(ty::AdtDef::did) {
cx.tcx.inherent_impls(ty_did).iter().any(|&did| { cx.tcx.inherent_impls(ty_did).iter().any(|&did| {
cx.tcx cx.tcx
@ -127,7 +142,11 @@ impl LateLintPass<'_> for IterWithoutIntoIter {
Mutability::Mut => sym::iter_mut, Mutability::Mut => sym::iter_mut,
Mutability::Not => sym::iter, Mutability::Not => sym::iter,
} }
&& !type_has_inherent_method(cx, ty, expected_method_name) && !deref_chain(cx, ty)
.any(|ty| {
// We can't check inherent impls for slices, but we know that they have an `iter(_mut)` method
ty.peel_refs().is_slice() || adt_has_inherent_method(cx, ty, expected_method_name)
})
&& let Some(iter_assoc_span) = imp.items.iter().find_map(|item| { && let Some(iter_assoc_span) = imp.items.iter().find_map(|item| {
if item.ident.name == sym!(IntoIter) { if item.ident.name == sym!(IntoIter) {
Some(cx.tcx.hir().impl_item(item.id).expect_type().span) Some(cx.tcx.hir().impl_item(item.id).expect_type().span)

View file

@ -50,9 +50,6 @@ extern crate clippy_utils;
#[macro_use] #[macro_use]
extern crate declare_clippy_lint; extern crate declare_clippy_lint;
use std::io;
use std::path::PathBuf;
use clippy_utils::msrvs::Msrv; use clippy_utils::msrvs::Msrv;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId}; use rustc_lint::{Lint, LintId};
@ -121,7 +118,6 @@ mod empty_structs_with_brackets;
mod endian_bytes; mod endian_bytes;
mod entry; mod entry;
mod enum_clike; mod enum_clike;
mod enum_variants;
mod equatable_if_let; mod equatable_if_let;
mod error_impl_error; mod error_impl_error;
mod escape; mod escape;
@ -166,6 +162,7 @@ mod inline_fn_without_body;
mod instant_subtraction; mod instant_subtraction;
mod int_plus_one; mod int_plus_one;
mod invalid_upcast_comparisons; mod invalid_upcast_comparisons;
mod item_name_repetitions;
mod items_after_statements; mod items_after_statements;
mod items_after_test_module; mod items_after_test_module;
mod iter_not_returning_iterator; mod iter_not_returning_iterator;
@ -362,7 +359,6 @@ mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints` // end lints modules, do not remove this comment, its used in `update_lints`
use crate::utils::conf::metadata::get_configuration_metadata; use crate::utils::conf::metadata::get_configuration_metadata;
use crate::utils::conf::TryConf;
pub use crate::utils::conf::{lookup_conf_file, Conf}; pub use crate::utils::conf::{lookup_conf_file, Conf};
use crate::utils::FindAll; use crate::utils::FindAll;
@ -374,65 +370,13 @@ use crate::utils::FindAll;
/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass.
/// ///
/// Used in `./src/driver.rs`. /// Used in `./src/driver.rs`.
pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
// NOTE: Do not add any more pre-expansion passes. These should be removed eventually. // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
let msrv = Msrv::read(&conf.msrv, sess); let msrv = || conf.msrv.clone();
let msrv = move || msrv.clone();
store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv: msrv() })); store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv: msrv() }));
} }
#[doc(hidden)]
pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
if let Ok((_, warnings)) = path {
for warning in warnings {
sess.warn(warning.clone());
}
}
let file_name = match path {
Ok((Some(path), _)) => path,
Ok((None, _)) => return Conf::default(),
Err(error) => {
sess.err(format!("error finding Clippy's configuration file: {error}"));
return Conf::default();
},
};
let TryConf { conf, errors, warnings } = utils::conf::read(sess, file_name);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
if let Some(span) = error.span {
sess.span_err(
span,
format!("error reading Clippy's configuration file: {}", error.message),
);
} else {
sess.err(format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
error.message
));
}
}
for warning in warnings {
if let Some(span) = warning.span {
sess.span_warn(
span,
format!("error reading Clippy's configuration file: {}", warning.message),
);
} else {
sess.warn(format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
warning.message
));
}
}
conf
}
#[derive(Default)] #[derive(Default)]
struct RegistrationGroups { struct RegistrationGroups {
all: Vec<LintId>, all: Vec<LintId>,
@ -558,7 +502,7 @@ fn register_categories(store: &mut rustc_lint::LintStore) {
/// ///
/// Used in `./src/driver.rs`. /// Used in `./src/driver.rs`.
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &'static Conf) {
register_removed_non_tool_lints(store); register_removed_non_tool_lints(store);
register_categories(store); register_categories(store);
@ -660,8 +604,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
let msrv = Msrv::read(&conf.msrv, sess); let msrv = || conf.msrv.clone();
let msrv = move || msrv.clone();
let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; let avoid_breaking_exported_api = conf.avoid_breaking_exported_api;
let allow_expect_in_tests = conf.allow_expect_in_tests; let allow_expect_in_tests = conf.allow_expect_in_tests;
let allow_unwrap_in_tests = conf.allow_unwrap_in_tests; let allow_unwrap_in_tests = conf.allow_unwrap_in_tests;
@ -762,6 +705,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
too_many_arguments_threshold, too_many_arguments_threshold,
too_many_lines_threshold, too_many_lines_threshold,
large_error_threshold, large_error_threshold,
avoid_breaking_exported_api,
)) ))
}); });
let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>(); let doc_valid_idents = conf.doc_valid_idents.iter().cloned().collect::<FxHashSet<_>>();
@ -806,7 +750,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
suppress_restriction_lint_in_const, suppress_restriction_lint_in_const,
)) ))
}); });
store.register_late_pass(|_| Box::new(non_copy_const::NonCopyConst)); let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
store.register_late_pass(move |_| Box::new(non_copy_const::NonCopyConst::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast)); store.register_late_pass(|_| Box::new(ptr_offset_with_cast::PtrOffsetWithCast));
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));
store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit));
@ -851,10 +796,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
)) ))
}); });
let enum_variant_name_threshold = conf.enum_variant_name_threshold; let enum_variant_name_threshold = conf.enum_variant_name_threshold;
let struct_field_name_threshold = conf.struct_field_name_threshold;
let allow_private_module_inception = conf.allow_private_module_inception; let allow_private_module_inception = conf.allow_private_module_inception;
store.register_late_pass(move |_| { store.register_late_pass(move |_| {
Box::new(enum_variants::EnumVariantNames::new( Box::new(item_name_repetitions::ItemNameRepetitions::new(
enum_variant_name_threshold, enum_variant_name_threshold,
struct_field_name_threshold,
avoid_breaking_exported_api, avoid_breaking_exported_api,
allow_private_module_inception, allow_private_module_inception,
)) ))

View file

@ -185,7 +185,7 @@ fn get_vec_push<'tcx>(
if let StmtKind::Semi(semi_stmt) = &stmt.kind; if let StmtKind::Semi(semi_stmt) = &stmt.kind;
if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind; if let ExprKind::MethodCall(path, self_expr, args, _) = &semi_stmt.kind;
// Figure out the parameters for the method call // Figure out the parameters for the method call
if let Some(pushed_item) = args.get(0); if let Some(pushed_item) = args.first();
// Check that the method being called is push() on a Vec // Check that the method being called is push() on a Vec
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec); if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
if path.ident.name.as_str() == "push"; if path.ident.name.as_str() == "push";

View file

@ -4,7 +4,7 @@ use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{ use rustc_hir::{
AsyncCoroutineKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, CoroutineKind, GenericArg, GenericBound, AsyncCoroutineKind, Block, Body, Closure, CoroutineKind, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, GenericBound,
ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, TypeBindingKind, ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, TypeBindingKind,
}; };
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -103,9 +103,9 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<
if let ExprKind::Path(ref count_func_qpath) = count_func.kind; if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
if let QPath::Resolved(_, count_func_path) = count_func_qpath; if let QPath::Resolved(_, count_func_path) = count_func_qpath;
if let Some(segment_zero) = count_func_path.segments.get(0); if let Some(segment_zero) = count_func_path.segments.first();
if let Some(args) = segment_zero.args; if let Some(args) = segment_zero.args;
if let Some(GenericArg::Type(real_ty)) = args.args.get(0); if let Some(GenericArg::Type(real_ty)) = args.args.first();
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id(); if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id); if cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id);

View file

@ -15,12 +15,13 @@ use rustc_span::{sym, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
/// Suggests to use dedicated built-in methods, /// Suggests to use dedicated built-in methods,
/// `is_ascii_(lowercase|uppercase|digit)` for checking on corresponding ascii range /// `is_ascii_(lowercase|uppercase|digit|hexdigit)` for checking on corresponding
/// ascii range
/// ///
/// ### Why is this bad? /// ### Why is this bad?
/// Using the built-in functions is more readable and makes it /// Using the built-in functions is more readable and makes it
/// clear that it's not a specific subset of characters, but all /// clear that it's not a specific subset of characters, but all
/// ASCII (lowercase|uppercase|digit) characters. /// ASCII (lowercase|uppercase|digit|hexdigit) characters.
/// ### Example /// ### Example
/// ```rust /// ```rust
/// fn main() { /// fn main() {
@ -28,6 +29,7 @@ declare_clippy_lint! {
/// assert!(matches!(b'X', b'A'..=b'Z')); /// assert!(matches!(b'X', b'A'..=b'Z'));
/// assert!(matches!('2', '0'..='9')); /// assert!(matches!('2', '0'..='9'));
/// assert!(matches!('x', 'A'..='Z' | 'a'..='z')); /// assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
/// assert!(matches!('C', '0'..='9' | 'a'..='f' | 'A'..='F'));
/// ///
/// ('0'..='9').contains(&'0'); /// ('0'..='9').contains(&'0');
/// ('a'..='z').contains(&'a'); /// ('a'..='z').contains(&'a');
@ -41,6 +43,7 @@ declare_clippy_lint! {
/// assert!(b'X'.is_ascii_uppercase()); /// assert!(b'X'.is_ascii_uppercase());
/// assert!('2'.is_ascii_digit()); /// assert!('2'.is_ascii_digit());
/// assert!('x'.is_ascii_alphabetic()); /// assert!('x'.is_ascii_alphabetic());
/// assert!('C'.is_ascii_hexdigit());
/// ///
/// '0'.is_ascii_digit(); /// '0'.is_ascii_digit();
/// 'a'.is_ascii_lowercase(); /// 'a'.is_ascii_lowercase();
@ -75,6 +78,12 @@ enum CharRange {
FullChar, FullChar,
/// '0..=9' /// '0..=9'
Digit, Digit,
/// 'a..=f'
LowerHexLetter,
/// 'A..=F'
UpperHexLetter,
/// '0..=9' | 'a..=f' | 'A..=F'
HexDigit,
Otherwise, Otherwise,
} }
@ -116,7 +125,8 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
CharRange::LowerChar => Some("is_ascii_lowercase"), CharRange::LowerChar => Some("is_ascii_lowercase"),
CharRange::FullChar => Some("is_ascii_alphabetic"), CharRange::FullChar => Some("is_ascii_alphabetic"),
CharRange::Digit => Some("is_ascii_digit"), CharRange::Digit => Some("is_ascii_digit"),
CharRange::Otherwise => None, CharRange::HexDigit => Some("is_ascii_hexdigit"),
CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => None,
} { } {
let default_snip = ".."; let default_snip = "..";
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;
@ -141,6 +151,12 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange {
if ranges.len() == 2 && ranges.contains(&CharRange::UpperChar) && ranges.contains(&CharRange::LowerChar) { if ranges.len() == 2 && ranges.contains(&CharRange::UpperChar) && ranges.contains(&CharRange::LowerChar) {
CharRange::FullChar CharRange::FullChar
} else if ranges.len() == 3
&& ranges.contains(&CharRange::Digit)
&& ranges.contains(&CharRange::LowerHexLetter)
&& ranges.contains(&CharRange::UpperHexLetter)
{
CharRange::HexDigit
} else { } else {
CharRange::Otherwise CharRange::Otherwise
} }
@ -156,6 +172,8 @@ fn check_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange {
match (&start_lit.node, &end_lit.node) { match (&start_lit.node, &end_lit.node) {
(Char('a'), Char('z')) | (Byte(b'a'), Byte(b'z')) => CharRange::LowerChar, (Char('a'), Char('z')) | (Byte(b'a'), Byte(b'z')) => CharRange::LowerChar,
(Char('A'), Char('Z')) | (Byte(b'A'), Byte(b'Z')) => CharRange::UpperChar, (Char('A'), Char('Z')) | (Byte(b'A'), Byte(b'Z')) => CharRange::UpperChar,
(Char('a'), Char('f')) | (Byte(b'a'), Byte(b'f')) => CharRange::LowerHexLetter,
(Char('A'), Char('F')) | (Byte(b'A'), Byte(b'F')) => CharRange::UpperHexLetter,
(Char('0'), Char('9')) | (Byte(b'0'), Byte(b'9')) => CharRange::Digit, (Char('0'), Char('9')) | (Byte(b'0'), Byte(b'9')) => CharRange::Digit,
_ => CharRange::Otherwise, _ => CharRange::Otherwise,
} }

View file

@ -967,7 +967,6 @@ declare_clippy_lint! {
"checks for unnecessary guards in match expressions" "checks for unnecessary guards in match expressions"
} }
#[derive(Default)]
pub struct Matches { pub struct Matches {
msrv: Msrv, msrv: Msrv,
infallible_destructuring_match_linted: bool, infallible_destructuring_match_linted: bool,
@ -978,7 +977,7 @@ impl Matches {
pub fn new(msrv: Msrv) -> Self { pub fn new(msrv: Msrv) -> Self {
Self { Self {
msrv, msrv,
..Matches::default() infallible_destructuring_match_linted: false,
} }
} }
} }

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_expr_identity_function, is_trait_method}; use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -9,7 +9,7 @@ use rustc_span::sym;
use super::FILTER_MAP_IDENTITY; use super::FILTER_MAP_IDENTITY;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) { pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_map_arg: &hir::Expr<'_>, filter_map_span: Span) {
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, filter_map_arg) { if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, filter_map_arg) {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
FILTER_MAP_IDENTITY, FILTER_MAP_IDENTITY,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{is_expr_identity_function, is_trait_method}; use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -15,7 +15,7 @@ pub(super) fn check<'tcx>(
flat_map_arg: &'tcx hir::Expr<'_>, flat_map_arg: &'tcx hir::Expr<'_>,
flat_map_span: Span, flat_map_span: Span,
) { ) {
if is_trait_method(cx, expr, sym::Iterator) && is_expr_identity_function(cx, flat_map_arg) { if is_trait_method(cx, expr, sym::Iterator) && is_expr_untyped_identity_function(cx, flat_map_arg) {
span_lint_and_sugg( span_lint_and_sugg(
cx, cx,
FLAT_MAP_IDENTITY, FLAT_MAP_IDENTITY,

View file

@ -1,5 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_slice_of_primitives;
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::LitKind; use rustc_ast::LitKind;
@ -20,7 +19,6 @@ pub(super) fn check<'tcx>(
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id); if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if let Some(impl_id) = cx.tcx.impl_of_method(method_id); if let Some(impl_id) = cx.tcx.impl_of_method(method_id);
if cx.tcx.type_of(impl_id).instantiate_identity().is_slice(); if cx.tcx.type_of(impl_id).instantiate_identity().is_slice();
if let Some(_) = is_slice_of_primitives(cx, recv);
if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind; if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = arg.kind;
then { then {
let mut app = Applicability::MachineApplicable; let mut app = Applicability::MachineApplicable;

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{is_expr_identity_function, is_trait_method}; use clippy_utils::{is_expr_untyped_identity_function, is_trait_method};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -23,7 +23,7 @@ pub(super) fn check(
if is_trait_method(cx, expr, sym::Iterator) if is_trait_method(cx, expr, sym::Iterator)
|| is_type_diagnostic_item(cx, caller_ty, sym::Result) || is_type_diagnostic_item(cx, caller_ty, sym::Result)
|| is_type_diagnostic_item(cx, caller_ty, sym::Option); || is_type_diagnostic_item(cx, caller_ty, sym::Option);
if is_expr_identity_function(cx, map_arg); if is_expr_untyped_identity_function(cx, map_arg);
if let Some(sugg_span) = expr.span.trim_start(caller.span); if let Some(sugg_span) = expr.span.trim_start(caller.span);
then { then {
span_lint_and_sugg( span_lint_and_sugg(

View file

@ -39,7 +39,7 @@ pub(super) fn check<'tcx>(
if search_method == "find"; if search_method == "find";
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind; if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = search_arg.kind;
let closure_body = cx.tcx.hir().body(body); let closure_body = cx.tcx.hir().body(body);
if let Some(closure_arg) = closure_body.params.get(0); if let Some(closure_arg) = closure_body.params.first();
then { then {
if let hir::PatKind::Ref(..) = closure_arg.pat.kind { if let hir::PatKind::Ref(..) = closure_arg.pat.kind {
Some(search_snippet.replacen('&', "", 1)) Some(search_snippet.replacen('&', "", 1))

View file

@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{eager_or_lazy, is_from_proc_macro, usage}; use clippy_utils::{eager_or_lazy, is_from_proc_macro, usage};
use hir::FnRetTy;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_lint::LateContext; use rustc_lint::LateContext;
@ -27,7 +28,7 @@ pub(super) fn check<'tcx>(
let is_bool = cx.typeck_results().expr_ty(recv).is_bool(); let is_bool = cx.typeck_results().expr_ty(recv).is_bool();
if is_option || is_result || is_bool { if is_option || is_result || is_bool {
if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind { if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl, .. }) = arg.kind {
let body = cx.tcx.hir().body(body); let body = cx.tcx.hir().body(body);
let body_expr = &body.value; let body_expr = &body.value;
@ -48,7 +49,14 @@ pub(super) fn check<'tcx>(
.iter() .iter()
// bindings are checked to be unused above // bindings are checked to be unused above
.all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild)) .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild))
{ && matches!(
fn_decl.output,
FnRetTy::DefaultReturn(_)
| FnRetTy::Return(hir::Ty {
kind: hir::TyKind::Infer,
..
})
) {
Applicability::MachineApplicable Applicability::MachineApplicable
} else { } else {
// replacing the lambda may break type inference // replacing the lambda may break type inference

View file

@ -67,7 +67,7 @@ impl MissingDoc {
if_chain! { if_chain! {
if let Some(meta) = meta; if let Some(meta) = meta;
if let MetaItemKind::List(list) = meta.kind; if let MetaItemKind::List(list) = meta.kind;
if let Some(meta) = list.get(0); if let Some(meta) = list.first();
if let Some(name) = meta.ident(); if let Some(name) = meta.ident();
then { then {
name.name == sym::include name.name == sym::include

View file

@ -17,6 +17,9 @@ declare_clippy_lint! {
/// Checks for imports that do not rename the item as specified /// Checks for imports that do not rename the item as specified
/// in the `enforce-import-renames` config option. /// in the `enforce-import-renames` config option.
/// ///
/// Note: Even though this lint is warn-by-default, it will only trigger if
/// import renames are defined in the clippy.toml file.
///
/// ### Why is this bad? /// ### Why is this bad?
/// Consistency is important, if a project has defined import /// Consistency is important, if a project has defined import
/// renames they should be followed. More practically, some item names are too /// renames they should be followed. More practically, some item names are too
@ -38,7 +41,7 @@ declare_clippy_lint! {
/// ``` /// ```
#[clippy::version = "1.55.0"] #[clippy::version = "1.55.0"]
pub MISSING_ENFORCED_IMPORT_RENAMES, pub MISSING_ENFORCED_IMPORT_RENAMES,
restriction, style,
"enforce import renames" "enforce import renames"
} }

View file

@ -1,9 +1,9 @@
use std::ops::ControlFlow; use std::ops::ControlFlow;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_path_lang_item;
use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::{for_each_expr, Visitable}; use clippy_utils::visitors::{for_each_expr, Visitable};
use clippy_utils::is_path_lang_item;
use rustc_ast::LitKind; use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};

View file

@ -9,7 +9,7 @@ use rustc_lint::{LateContext, LateLintPass};
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, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span; use rustc_span::{DesugaringKind, Span};
declare_clippy_lint! { declare_clippy_lint! {
/// ### What it does /// ### What it does
@ -64,7 +64,10 @@ declare_lint_pass!(MultipleUnsafeOpsPerBlock => [MULTIPLE_UNSAFE_OPS_PER_BLOCK])
impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock { impl<'tcx> LateLintPass<'tcx> for MultipleUnsafeOpsPerBlock {
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) {
if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) || in_external_macro(cx.tcx.sess, block.span) { if !matches!(block.rules, BlockCheckMode::UnsafeBlock(_))
|| in_external_macro(cx.tcx.sess, block.span)
|| block.span.is_desugaring(DesugaringKind::Await)
{
return; return;
} }
let mut unsafe_ops = vec![]; let mut unsafe_ops = vec![];

View file

@ -189,7 +189,7 @@ fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>)
} }
fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool { fn is_first_block_stmt_continue(block: &ast::Block, label: Option<&ast::Label>) -> bool {
block.stmts.get(0).map_or(false, |stmt| match stmt.kind { block.stmts.first().map_or(false, |stmt| match stmt.kind {
ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => { ast::StmtKind::Semi(ref e) | ast::StmtKind::Expr(ref e) => {
if let ast::ExprKind::Continue(ref l) = e.kind { if let ast::ExprKind::Continue(ref l) = e.kind {
compare_labels(label, l.as_ref()) compare_labels(label, l.as_ref())
@ -434,7 +434,7 @@ fn erode_from_back(s: &str) -> String {
} }
fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> { fn span_of_first_expr_in_block(block: &ast::Block) -> Option<Span> {
block.stmts.get(0).map(|stmt| stmt.span) block.stmts.first().map(|stmt| stmt.span)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -7,7 +7,8 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor}; use rustc_hir::intravisit::{walk_qpath, FnKind, Visitor};
use rustc_hir::{ use rustc_hir::{
Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node, PatKind, QPath, BlockCheckMode, Body, Closure, Expr, ExprKind, FnDecl, HirId, HirIdMap, HirIdSet, Impl, ItemKind, Mutability, Node,
PatKind, QPath,
}; };
use rustc_hir_typeck::expr_use_visitor as euv; use rustc_hir_typeck::expr_use_visitor as euv;
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
@ -139,13 +140,23 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id); let hir_id = cx.tcx.hir().local_def_id_to_hir_id(fn_def_id);
let is_async = match kind { let is_async = match kind {
FnKind::ItemFn(.., header) => { FnKind::ItemFn(.., header) => {
if header.is_unsafe() {
// We don't check unsafe functions.
return;
}
let attrs = cx.tcx.hir().attrs(hir_id); let attrs = cx.tcx.hir().attrs(hir_id);
if header.abi != Abi::Rust || requires_exact_signature(attrs) { if header.abi != Abi::Rust || requires_exact_signature(attrs) {
return; return;
} }
header.is_async() header.is_async()
}, },
FnKind::Method(.., sig) => sig.header.is_async(), FnKind::Method(.., sig) => {
if sig.header.is_unsafe() {
// We don't check unsafe functions.
return;
}
sig.header.is_async()
},
FnKind::Closure => return, FnKind::Closure => return,
}; };
@ -186,20 +197,21 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
}; };
let infcx = cx.tcx.infer_ctxt().build(); let infcx = cx.tcx.infer_ctxt().build();
euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body); euv::ExprUseVisitor::new(&mut ctx, &infcx, fn_def_id, cx.param_env, cx.typeck_results()).consume_body(body);
let mut checked_closures = FxHashSet::default();
// We retrieve all the closures declared in the function because they will not be found
// by `euv::Delegate`.
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
for_each_expr_with_closures(cx, body, |expr| {
if let ExprKind::Closure(closure) = expr.kind {
closures.insert(closure.def_id);
}
ControlFlow::<()>::Continue(())
});
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
if is_async { if is_async {
let mut checked_closures = FxHashSet::default();
// We retrieve all the closures declared in the async function because they will
// not be found by `euv::Delegate`.
let mut closures: FxHashSet<LocalDefId> = FxHashSet::default();
for_each_expr_with_closures(cx, body, |expr| {
if let ExprKind::Closure(closure) = expr.kind {
closures.insert(closure.def_id);
}
ControlFlow::<()>::Continue(())
});
check_closures(&mut ctx, cx, &infcx, &mut checked_closures, closures);
while !ctx.async_closures.is_empty() { while !ctx.async_closures.is_empty() {
let async_closures = ctx.async_closures.clone(); let async_closures = ctx.async_closures.clone();
ctx.async_closures.clear(); ctx.async_closures.clear();
@ -304,10 +316,27 @@ impl<'tcx> MutablyUsedVariablesCtxt<'tcx> {
} }
self.aliases.insert(alias, target); self.aliases.insert(alias, target);
} }
// The goal here is to find if the current scope is unsafe or not. It stops when it finds
// a function or an unsafe block.
fn is_in_unsafe_block(&self, item: HirId) -> bool {
let hir = self.tcx.hir();
for (parent, node) in hir.parent_iter(item) {
if let Some(fn_sig) = hir.fn_sig_by_hir_id(parent) {
return fn_sig.header.is_unsafe();
} else if let Node::Block(block) = node {
if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) {
return true;
}
}
}
false
}
} }
impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) { #[allow(clippy::if_same_then_else)]
fn consume(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
if let euv::Place { if let euv::Place {
base: base:
euv::PlaceBase::Local(vid) euv::PlaceBase::Local(vid)
@ -327,13 +356,18 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
&& matches!(base_ty.ref_mutability(), Some(Mutability::Mut)) && matches!(base_ty.ref_mutability(), Some(Mutability::Mut))
{ {
self.add_mutably_used_var(*vid); self.add_mutably_used_var(*vid);
} else if self.is_in_unsafe_block(id) {
// If we are in an unsafe block, any operation on this variable must not be warned
// upon!
self.add_mutably_used_var(*vid);
} }
self.prev_bind = None; self.prev_bind = None;
self.prev_move_to_closure.remove(vid); self.prev_move_to_closure.remove(vid);
} }
} }
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId, borrow: ty::BorrowKind) { #[allow(clippy::if_same_then_else)]
fn borrow(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId, borrow: ty::BorrowKind) {
self.prev_bind = None; self.prev_bind = None;
if let euv::Place { if let euv::Place {
base: base:
@ -355,6 +389,10 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
|| (borrow == ty::BorrowKind::UniqueImmBorrow && base_ty.ref_mutability() == Some(Mutability::Mut)) || (borrow == ty::BorrowKind::UniqueImmBorrow && base_ty.ref_mutability() == Some(Mutability::Mut))
{ {
self.add_mutably_used_var(*vid); self.add_mutably_used_var(*vid);
} else if self.is_in_unsafe_block(id) {
// If we are in an unsafe block, any operation on this variable must not be warned
// upon!
self.add_mutably_used_var(*vid);
} }
} else if borrow == ty::ImmBorrow { } else if borrow == ty::ImmBorrow {
// If there is an `async block`, it'll contain a call to a closure which we need to // If there is an `async block`, it'll contain a call to a closure which we need to
@ -397,7 +435,21 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
} }
} }
fn copy(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, _id: HirId) { fn copy(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
if let euv::Place {
base:
euv::PlaceBase::Local(vid)
| euv::PlaceBase::Upvar(UpvarId {
var_path: UpvarPath { hir_id: vid },
..
}),
..
} = &cmt.place
{
if self.is_in_unsafe_block(id) {
self.add_mutably_used_var(*vid);
}
}
self.prev_bind = None; self.prev_bind = None;
} }
@ -427,8 +479,22 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> {
} }
} }
fn bind(&mut self, _cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) { fn bind(&mut self, cmt: &euv::PlaceWithHirId<'tcx>, id: HirId) {
self.prev_bind = Some(id); self.prev_bind = Some(id);
if let euv::Place {
base:
euv::PlaceBase::Local(vid)
| euv::PlaceBase::Upvar(UpvarId {
var_path: UpvarPath { hir_id: vid },
..
}),
..
} = &cmt.place
{
if self.is_in_unsafe_block(id) {
self.add_mutably_used_var(*vid);
}
}
} }
} }

View file

@ -1,10 +1,10 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::is_self;
use clippy_utils::ptr::get_spans; use clippy_utils::ptr::get_spans;
use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::source::{snippet, snippet_opt};
use clippy_utils::ty::{ use clippy_utils::ty::{
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item, implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
}; };
use clippy_utils::is_self;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::Attribute; use rustc_ast::ast::Attribute;
use rustc_errors::{Applicability, Diagnostic}; use rustc_errors::{Applicability, Diagnostic};

View file

@ -4,7 +4,7 @@ use clippy_utils::source::snippet;
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::{AsyncCoroutineKind, Block, Body, Expr, ExprKind, CoroutineKind, LangItem, MatchSource, QPath}; use rustc_hir::{AsyncCoroutineKind, Block, Body, CoroutineKind, Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};

View file

@ -5,9 +5,10 @@
use std::ptr; use std::ptr;
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::in_constant;
use clippy_utils::macros::macro_backtrace; use clippy_utils::macros::macro_backtrace;
use clippy_utils::{def_path_def_ids, in_constant};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{ use rustc_hir::{
@ -15,9 +16,10 @@ use rustc_hir::{
}; };
use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_lint::{LateContext, LateLintPass, Lint};
use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId}; use rustc_middle::mir::interpret::{ErrorHandled, EvalToValTreeResult, GlobalId};
use rustc_middle::query::Key;
use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::adjustment::Adjust;
use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, InnerSpan, Span}; use rustc_span::{sym, InnerSpan, Span};
use rustc_target::abi::VariantIdx; use rustc_target::abi::VariantIdx;
@ -126,128 +128,6 @@ declare_clippy_lint! {
"referencing `const` with interior mutability" "referencing `const` with interior mutability"
} }
fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
// Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
// making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
// 'unfrozen'. However, this code causes a false negative in which
// a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
// Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
// since it works when a pointer indirection involves (`Cell<*const T>`).
// Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
// but I'm not sure whether it's a decent way, if possible.
cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx, cx.param_env)
}
fn is_value_unfrozen_raw<'tcx>(
cx: &LateContext<'tcx>,
result: Result<Option<ty::ValTree<'tcx>>, ErrorHandled>,
ty: Ty<'tcx>,
) -> bool {
fn inner<'tcx>(cx: &LateContext<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> bool {
match *ty.kind() {
// the fact that we have to dig into every structs to search enums
// leads us to the point checking `UnsafeCell` directly is the only option.
ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true,
// As of 2022-09-08 miri doesn't track which union field is active so there's no safe way to check the
// contained value.
ty::Adt(def, ..) if def.is_union() => false,
ty::Array(ty, _) => val.unwrap_branch().iter().any(|field| inner(cx, *field, ty)),
ty::Adt(def, _) if def.is_union() => false,
ty::Adt(def, args) if def.is_enum() => {
let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap();
let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap());
fields
.iter()
.copied()
.zip(
def.variants()[variant_index]
.fields
.iter()
.map(|field| field.ty(cx.tcx, args)),
)
.any(|(field, ty)| inner(cx, field, ty))
},
ty::Adt(def, args) => val
.unwrap_branch()
.iter()
.zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args)))
.any(|(field, ty)| inner(cx, *field, ty)),
ty::Tuple(tys) => val
.unwrap_branch()
.iter()
.zip(tys)
.any(|(field, ty)| inner(cx, *field, ty)),
_ => false,
}
}
result.map_or_else(
|err| {
// Consider `TooGeneric` cases as being unfrozen.
// This causes a false positive where an assoc const whose type is unfrozen
// have a value that is a frozen variant with a generic param (an example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
// However, it prevents a number of false negatives that is, I think, important:
// 1. assoc consts in trait defs referring to consts of themselves (an example is
// `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
// 2. a path expr referring to assoc consts whose type is doesn't have any frozen variants in trait
// defs (i.e. without substitute for `Self`). (e.g. borrowing
// `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
// 3. similar to the false positive above; but the value is an unfrozen variant, or the type has no
// enums. (An example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` and
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
// One might be able to prevent these FNs correctly, and replace this with `false`;
// e.g. implementing `has_frozen_variant` described above, and not running this function
// when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
// case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
// similar to 2., but with the a frozen variant) (e.g. borrowing
// `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
// I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
matches!(err, ErrorHandled::TooGeneric(..))
},
|val| val.map_or(true, |val| inner(cx, val, ty)),
)
}
fn is_value_unfrozen_poly<'tcx>(cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
let def_id = body_id.hir_id.owner.to_def_id();
let args = ty::GenericArgs::identity_for_item(cx.tcx, def_id);
let instance = ty::Instance::new(def_id, args);
let cid = rustc_middle::mir::interpret::GlobalId {
instance,
promoted: None,
};
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, None);
is_value_unfrozen_raw(cx, result, ty)
}
fn is_value_unfrozen_expr<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
let args = cx.typeck_results().node_args(hir_id);
let result = const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), None);
is_value_unfrozen_raw(cx, result, ty)
}
pub fn const_eval_resolve<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ct: ty::UnevaluatedConst<'tcx>,
span: Option<Span>,
) -> EvalToValTreeResult<'tcx> {
match ty::Instance::resolve(tcx, param_env, ct.def, ct.args) {
Ok(Some(instance)) => {
let cid = GlobalId {
instance,
promoted: None,
};
tcx.const_eval_global_id_for_typeck(param_env, cid, span)
},
Ok(None) => Err(ErrorHandled::TooGeneric(span.unwrap_or(rustc_span::DUMMY_SP))),
Err(err) => Err(ErrorHandled::Reported(err.into(), span.unwrap_or(rustc_span::DUMMY_SP))),
}
}
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
enum Source { enum Source {
Item { item: Span }, Item { item: Span },
@ -292,13 +172,178 @@ fn lint(cx: &LateContext<'_>, source: Source) {
}); });
} }
declare_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]); #[derive(Clone)]
pub struct NonCopyConst {
ignore_interior_mutability: Vec<String>,
ignore_mut_def_ids: FxHashSet<DefId>,
}
impl_lint_pass!(NonCopyConst => [DECLARE_INTERIOR_MUTABLE_CONST, BORROW_INTERIOR_MUTABLE_CONST]);
impl NonCopyConst {
pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
Self {
ignore_interior_mutability,
ignore_mut_def_ids: FxHashSet::default(),
}
}
fn is_ty_ignored(&self, ty: Ty<'_>) -> bool {
matches!(ty.ty_adt_id(), Some(adt_id) if self.ignore_mut_def_ids.contains(&adt_id))
}
fn is_unfrozen<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
// Ignore types whose layout is unknown since `is_freeze` reports every generic types as `!Freeze`,
// making it indistinguishable from `UnsafeCell`. i.e. it isn't a tool to prove a type is
// 'unfrozen'. However, this code causes a false negative in which
// a type contains a layout-unknown type, but also an unsafe cell like `const CELL: Cell<T>`.
// Yet, it's better than `ty.has_type_flags(TypeFlags::HAS_TY_PARAM | TypeFlags::HAS_PROJECTION)`
// since it works when a pointer indirection involves (`Cell<*const T>`).
// Making up a `ParamEnv` where every generic params and assoc types are `Freeze`is another option;
// but I'm not sure whether it's a decent way, if possible.
cx.tcx.layout_of(cx.param_env.and(ty)).is_ok() && !ty.is_freeze(cx.tcx, cx.param_env)
}
fn is_value_unfrozen_raw_inner<'tcx>(&self, cx: &LateContext<'tcx>, val: ty::ValTree<'tcx>, ty: Ty<'tcx>) -> bool {
if self.is_ty_ignored(ty) {
return false;
}
match *ty.kind() {
// the fact that we have to dig into every structs to search enums
// leads us to the point checking `UnsafeCell` directly is the only option.
ty::Adt(ty_def, ..) if ty_def.is_unsafe_cell() => true,
// As of 2022-09-08 miri doesn't track which union field is active so there's no safe way to check the
// contained value.
ty::Adt(def, ..) if def.is_union() => false,
ty::Array(ty, _) => val
.unwrap_branch()
.iter()
.any(|field| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
ty::Adt(def, _) if def.is_union() => false,
ty::Adt(def, args) if def.is_enum() => {
let (&variant_index, fields) = val.unwrap_branch().split_first().unwrap();
let variant_index = VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap());
fields
.iter()
.copied()
.zip(
def.variants()[variant_index]
.fields
.iter()
.map(|field| field.ty(cx.tcx, args)),
)
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, field, ty))
},
ty::Adt(def, args) => val
.unwrap_branch()
.iter()
.zip(def.non_enum_variant().fields.iter().map(|field| field.ty(cx.tcx, args)))
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
ty::Tuple(tys) => val
.unwrap_branch()
.iter()
.zip(tys)
.any(|(field, ty)| self.is_value_unfrozen_raw_inner(cx, *field, ty)),
_ => false,
}
}
fn is_value_unfrozen_raw<'tcx>(
&self,
cx: &LateContext<'tcx>,
result: Result<Option<ty::ValTree<'tcx>>, ErrorHandled>,
ty: Ty<'tcx>,
) -> bool {
result.map_or_else(
|err| {
// Consider `TooGeneric` cases as being unfrozen.
// This causes a false positive where an assoc const whose type is unfrozen
// have a value that is a frozen variant with a generic param (an example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::GENERIC_VARIANT`).
// However, it prevents a number of false negatives that is, I think, important:
// 1. assoc consts in trait defs referring to consts of themselves (an example is
// `declare_interior_mutable_const::traits::ConcreteTypes::ANOTHER_ATOMIC`).
// 2. a path expr referring to assoc consts whose type is doesn't have any frozen variants in trait
// defs (i.e. without substitute for `Self`). (e.g. borrowing
// `borrow_interior_mutable_const::trait::ConcreteTypes::ATOMIC`)
// 3. similar to the false positive above; but the value is an unfrozen variant, or the type has no
// enums. (An example is
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::UNFROZEN_VARIANT` and
// `declare_interior_mutable_const::enums::BothOfCellAndGeneric::NO_ENUM`).
// One might be able to prevent these FNs correctly, and replace this with `false`;
// e.g. implementing `has_frozen_variant` described above, and not running this function
// when the type doesn't have any frozen variants would be the 'correct' way for the 2nd
// case (that actually removes another suboptimal behavior (I won't say 'false positive') where,
// similar to 2., but with the a frozen variant) (e.g. borrowing
// `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`).
// I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none).
matches!(err, ErrorHandled::TooGeneric(..))
},
|val| val.map_or(true, |val| self.is_value_unfrozen_raw_inner(cx, val, ty)),
)
}
fn is_value_unfrozen_poly<'tcx>(&self, cx: &LateContext<'tcx>, body_id: BodyId, ty: Ty<'tcx>) -> bool {
let def_id = body_id.hir_id.owner.to_def_id();
let args = ty::GenericArgs::identity_for_item(cx.tcx, def_id);
let instance = ty::Instance::new(def_id, args);
let cid = rustc_middle::mir::interpret::GlobalId {
instance,
promoted: None,
};
let param_env = cx.tcx.param_env(def_id).with_reveal_all_normalized(cx.tcx);
let result = cx.tcx.const_eval_global_id_for_typeck(param_env, cid, None);
self.is_value_unfrozen_raw(cx, result, ty)
}
fn is_value_unfrozen_expr<'tcx>(&self, cx: &LateContext<'tcx>, hir_id: HirId, def_id: DefId, ty: Ty<'tcx>) -> bool {
let args = cx.typeck_results().node_args(hir_id);
let result = Self::const_eval_resolve(cx.tcx, cx.param_env, ty::UnevaluatedConst::new(def_id, args), None);
self.is_value_unfrozen_raw(cx, result, ty)
}
pub fn const_eval_resolve<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ct: ty::UnevaluatedConst<'tcx>,
span: Option<Span>,
) -> EvalToValTreeResult<'tcx> {
match ty::Instance::resolve(tcx, param_env, ct.def, ct.args) {
Ok(Some(instance)) => {
let cid = GlobalId {
instance,
promoted: None,
};
tcx.const_eval_global_id_for_typeck(param_env, cid, span)
},
Ok(None) => Err(ErrorHandled::TooGeneric(span.unwrap_or(rustc_span::DUMMY_SP))),
Err(err) => Err(ErrorHandled::Reported(err.into(), span.unwrap_or(rustc_span::DUMMY_SP))),
}
}
}
impl<'tcx> LateLintPass<'tcx> for NonCopyConst { impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
self.ignore_mut_def_ids.clear();
let mut path = Vec::new();
for ty in &self.ignore_interior_mutability {
path.extend(ty.split("::"));
for id in def_path_def_ids(cx, &path[..]) {
self.ignore_mut_def_ids.insert(id);
}
path.clear();
}
}
fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) {
if let ItemKind::Const(.., body_id) = it.kind { if let ItemKind::Const(.., body_id) = it.kind {
let ty = cx.tcx.type_of(it.owner_id).instantiate_identity(); let ty = cx.tcx.type_of(it.owner_id).instantiate_identity();
if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) { if !ignored_macro(cx, it)
&& !self.is_ty_ignored(ty)
&& Self::is_unfrozen(cx, ty)
&& self.is_value_unfrozen_poly(cx, body_id, ty)
{
lint(cx, Source::Item { item: it.span }); lint(cx, Source::Item { item: it.span });
} }
} }
@ -311,7 +356,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// Normalize assoc types because ones originated from generic params // Normalize assoc types because ones originated from generic params
// bounded other traits could have their bound. // bounded other traits could have their bound.
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, normalized) if !self.is_ty_ignored(ty) && Self::is_unfrozen(cx, normalized)
// When there's no default value, lint it only according to its type; // When there's no default value, lint it only according to its type;
// in other words, lint consts whose value *could* be unfrozen, not definitely is. // in other words, lint consts whose value *could* be unfrozen, not definitely is.
// This feels inconsistent with how the lint treats generic types, // This feels inconsistent with how the lint treats generic types,
@ -324,7 +369,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// i.e. having an enum doesn't necessary mean a type has a frozen variant. // i.e. having an enum doesn't necessary mean a type has a frozen variant.
// And, implementing it isn't a trivial task; it'll probably end up // And, implementing it isn't a trivial task; it'll probably end up
// re-implementing the trait predicate evaluation specific to `Freeze`. // re-implementing the trait predicate evaluation specific to `Freeze`.
&& body_id_opt.map_or(true, |body_id| is_value_unfrozen_poly(cx, body_id, normalized)) && body_id_opt.map_or(true, |body_id| self.is_value_unfrozen_poly(cx, body_id, normalized))
{ {
lint(cx, Source::Assoc { item: trait_item.span }); lint(cx, Source::Assoc { item: trait_item.span });
} }
@ -367,8 +412,8 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// e.g. `layout_of(...).is_err() || has_frozen_variant(...);` // e.g. `layout_of(...).is_err() || has_frozen_variant(...);`
let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity(); let ty = cx.tcx.type_of(impl_item.owner_id).instantiate_identity();
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, normalized); if !self.is_ty_ignored(ty) && Self::is_unfrozen(cx, normalized);
if is_value_unfrozen_poly(cx, *body_id, normalized); if self.is_value_unfrozen_poly(cx, *body_id, normalized);
then { then {
lint( lint(
cx, cx,
@ -384,7 +429,10 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
// Normalize assoc types originated from generic params. // Normalize assoc types originated from generic params.
let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty); let normalized = cx.tcx.normalize_erasing_regions(cx.param_env, ty);
if is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, *body_id, normalized) { if !self.is_ty_ignored(ty)
&& Self::is_unfrozen(cx, ty)
&& self.is_value_unfrozen_poly(cx, *body_id, normalized)
{
lint(cx, Source::Assoc { item: impl_item.span }); lint(cx, Source::Assoc { item: impl_item.span });
} }
}, },
@ -478,7 +526,10 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst {
cx.typeck_results().expr_ty(dereferenced_expr) cx.typeck_results().expr_ty(dereferenced_expr)
}; };
if is_unfrozen(cx, ty) && is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty) { if !self.is_ty_ignored(ty)
&& Self::is_unfrozen(cx, ty)
&& self.is_value_unfrozen_expr(cx, expr.hir_id, item_def_id, ty)
{
lint(cx, Source::Expr { expr: expr.span }); lint(cx, Source::Expr { expr: expr.span });
} }
} }

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_lint_allowed;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::is_lint_allowed;
use rustc_ast::ImplPolarity; use rustc_ast::ImplPolarity;
use rustc_hir::def_id::DefId; use rustc_hir::def_id::DefId;
use rustc_hir::{FieldDef, Item, ItemKind, Node}; use rustc_hir::{FieldDef, Item, ItemKind, Node};

View file

@ -134,6 +134,7 @@ impl Usage {
/// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the /// The parameters being checked by the lint, indexed by both the parameter's `HirId` and the
/// `DefId` of the function paired with the parameter's index. /// `DefId` of the function paired with the parameter's index.
#[derive(Default)] #[derive(Default)]
#[allow(clippy::struct_field_names)]
struct Params { struct Params {
params: Vec<Param>, params: Vec<Param>,
by_id: HirIdMap<usize>, by_id: HirIdMap<usize>,

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::path_def_id;
use clippy_utils::source::snippet; use clippy_utils::source::snippet;
use clippy_utils::ty::{implements_trait, is_copy}; use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::path_def_id;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext; use rustc_lint::LateContext;

View file

@ -1,8 +1,8 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::VecArgs; use clippy_utils::higher::VecArgs;
use clippy_utils::last_path_segment;
use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::source::{indent_of, snippet}; use clippy_utils::source::{indent_of, snippet};
use clippy_utils::last_path_segment;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath, TyKind}; use rustc_hir::{Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -5,7 +5,7 @@ use clippy_utils::peel_blocks;
use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::visitors::for_each_expr; use clippy_utils::visitors::for_each_expr;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{AsyncCoroutineKind, Closure, Expr, ExprKind, CoroutineKind, MatchSource}; use rustc_hir::{AsyncCoroutineKind, Closure, CoroutineKind, Expr, ExprKind, MatchSource};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro; use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::UpvarCapture; use rustc_middle::ty::UpvarCapture;

View file

@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantLocals {
if let Res::Local(binding_id) = cx.qpath_res(&qpath, expr.hir_id); if let Res::Local(binding_id) = cx.qpath_res(&qpath, expr.hir_id);
if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id); if let Node::Pat(binding_pat) = cx.tcx.hir().get(binding_id);
// the previous binding has the same mutability // the previous binding has the same mutability
if find_binding(binding_pat, ident).unwrap().1 == mutability; if find_binding(binding_pat, ident).is_some_and(|bind| bind.1 == mutability);
// the local does not change the effect of assignments to the binding. see #11290 // the local does not change the effect of assignments to the binding. see #11290
if !affects_assignments(cx, mutability, binding_id, local.hir_id); if !affects_assignments(cx, mutability, binding_id, local.hir_id);
// the local does not affect the code's drop behavior // the local does not affect the code's drop behavior

View file

@ -335,7 +335,7 @@ impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> {
fn visit_block(&mut self, block: &'tcx Block<'_>) { fn visit_block(&mut self, block: &'tcx Block<'_>) {
if self.initialization_found { if self.initialization_found {
if let Some(s) = block.stmts.get(0) { if let Some(s) = block.stmts.first() {
self.visit_stmt(s); self.visit_stmt(s);
} }

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_context;
use clippy_utils::path_def_id; use clippy_utils::path_def_id;
use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp}; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, UnOp};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};

View file

@ -578,7 +578,7 @@ impl Types {
} }
} }
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
struct CheckTyContext { struct CheckTyContext {
is_in_trait_impl: bool, is_in_trait_impl: bool,

View file

@ -201,7 +201,7 @@ fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Opt
let expr = peel_hir_expr_while(expr, |e| { let expr = peel_hir_expr_while(expr, |e| {
if let ExprKind::Block(block, _) = e.kind { if let ExprKind::Block(block, _) = e.kind {
// Extract the first statement/expression // Extract the first statement/expression
match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) { match (block.stmts.first().map(|stmt| &stmt.kind), block.expr) {
(None, Some(expr)) => Some(expr), (None, Some(expr)) => Some(expr),
(Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr), (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr),
_ => None, _ => None,

View file

@ -40,7 +40,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
let (constructor_path, constructor_item) = let (constructor_path, constructor_item) =
if let hir::ExprKind::Call(constructor, constructor_args) = recv.kind if let hir::ExprKind::Call(constructor, constructor_args) = recv.kind
&& let hir::ExprKind::Path(constructor_path) = constructor.kind && let hir::ExprKind::Path(constructor_path) = constructor.kind
&& let Some(arg) = constructor_args.get(0) && let Some(arg) = constructor_args.first()
{ {
if constructor.span.from_expansion() || arg.span.from_expansion() { if constructor.span.from_expansion() || arg.span.from_expansion() {
return; return;
@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor {
_ => return, _ => return,
} }
if let Some(map_arg) = args.get(0) if let Some(map_arg) = args.first()
&& let hir::ExprKind::Path(fun) = map_arg.kind && let hir::ExprKind::Path(fun) = map_arg.kind
{ {
if map_arg.span.from_expansion() { if map_arg.span.from_expansion() {

View file

@ -54,7 +54,6 @@ declare_clippy_lint! {
"unnecessary structure name repetition whereas `Self` is applicable" "unnecessary structure name repetition whereas `Self` is applicable"
} }
#[derive(Default)]
pub struct UseSelf { pub struct UseSelf {
msrv: Msrv, msrv: Msrv,
stack: Vec<StackItem>, stack: Vec<StackItem>,
@ -65,7 +64,7 @@ impl UseSelf {
pub fn new(msrv: Msrv) -> Self { pub fn new(msrv: Msrv) -> Self {
Self { Self {
msrv, msrv,
..Self::default() stack: Vec::new(),
} }
} }
} }

View file

@ -268,8 +268,8 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
fn qpath(&self, qpath: &Binding<&QPath<'_>>) { fn qpath(&self, qpath: &Binding<&QPath<'_>>) {
if let QPath::LangItem(lang_item, ..) = *qpath.value { if let QPath::LangItem(lang_item, ..) = *qpath.value {
chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))"); chain!(self, "matches!({qpath}, QPath::LangItem(LangItem::{lang_item:?}, _))");
} else { } else if let Ok(path) = path_to_string(qpath.value) {
chain!(self, "match_qpath({qpath}, &[{}])", path_to_string(qpath.value)); chain!(self, "match_qpath({qpath}, &[{}])", path);
} }
} }
@ -738,8 +738,8 @@ fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool {
get_attr(cx.sess(), attrs, "author").count() > 0 get_attr(cx.sess(), attrs, "author").count() > 0
} }
fn path_to_string(path: &QPath<'_>) -> String { fn path_to_string(path: &QPath<'_>) -> Result<String, ()> {
fn inner(s: &mut String, path: &QPath<'_>) { fn inner(s: &mut String, path: &QPath<'_>) -> Result<(), ()> {
match *path { match *path {
QPath::Resolved(_, path) => { QPath::Resolved(_, path) => {
for (i, segment) in path.segments.iter().enumerate() { for (i, segment) in path.segments.iter().enumerate() {
@ -751,16 +751,18 @@ fn path_to_string(path: &QPath<'_>) -> String {
}, },
QPath::TypeRelative(ty, segment) => match &ty.kind { QPath::TypeRelative(ty, segment) => match &ty.kind {
hir::TyKind::Path(inner_path) => { hir::TyKind::Path(inner_path) => {
inner(s, inner_path); inner(s, inner_path)?;
*s += ", "; *s += ", ";
write!(s, "{:?}", segment.ident.as_str()).unwrap(); write!(s, "{:?}", segment.ident.as_str()).unwrap();
}, },
other => write!(s, "/* unimplemented: {other:?}*/").unwrap(), other => write!(s, "/* unimplemented: {other:?}*/").unwrap(),
}, },
QPath::LangItem(..) => panic!("path_to_string: called for lang item qpath"), QPath::LangItem(..) => return Err(()),
} }
Ok(())
} }
let mut s = String::new(); let mut s = String::new();
inner(&mut s, path); inner(&mut s, path)?;
s Ok(s)
} }

View file

@ -8,8 +8,9 @@ use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
use serde::Deserialize; use serde::Deserialize;
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::ops::Range; use std::ops::Range;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::sync::OnceLock;
use std::{cmp, env, fmt, fs, io}; use std::{cmp, env, fmt, fs, io};
#[rustfmt::skip] #[rustfmt::skip]
@ -78,62 +79,35 @@ pub struct TryConf {
impl TryConf { impl TryConf {
fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self { fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self {
ConfError::from_toml(file, error).into()
}
}
impl From<ConfError> for TryConf {
fn from(value: ConfError) -> Self {
Self { Self {
conf: Conf::default(), conf: Conf::default(),
errors: vec![value], errors: vec![ConfError::from_toml(file, error)],
warnings: vec![], warnings: vec![],
} }
} }
} }
impl From<io::Error> for TryConf {
fn from(value: io::Error) -> Self {
ConfError::from(value).into()
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct ConfError { pub struct ConfError {
pub message: String, pub message: String,
pub span: Option<Span>, pub span: Span,
} }
impl ConfError { impl ConfError {
fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self { fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self {
if let Some(span) = error.span() { let span = error.span().unwrap_or(0..file.source_len.0 as usize);
Self::spanned(file, error.message(), span) Self::spanned(file, error.message(), span)
} else {
Self {
message: error.message().to_string(),
span: None,
}
}
} }
fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self { fn spanned(file: &SourceFile, message: impl Into<String>, span: Range<usize>) -> Self {
Self { Self {
message: message.into(), message: message.into(),
span: Some(Span::new( span: Span::new(
file.start_pos + BytePos::from_usize(span.start), file.start_pos + BytePos::from_usize(span.start),
file.start_pos + BytePos::from_usize(span.end), file.start_pos + BytePos::from_usize(span.end),
SyntaxContext::root(), SyntaxContext::root(),
None, None,
)), ),
}
}
}
impl From<io::Error> for ConfError {
fn from(value: io::Error) -> Self {
Self {
message: value.to_string(),
span: None,
} }
} }
} }
@ -297,7 +271,7 @@ define_Conf! {
/// 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. /// 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.
/// ///
/// The minimum rust version that the project supports /// The minimum rust version that the project supports
(msrv: Option<String> = None), (msrv: crate::Msrv = crate::Msrv::empty()),
/// DEPRECATED LINT: BLACKLISTED_NAME. /// DEPRECATED LINT: BLACKLISTED_NAME.
/// ///
/// Use the Disallowed Names lint instead /// Use the Disallowed Names lint instead
@ -360,6 +334,10 @@ define_Conf! {
/// ///
/// The minimum number of enum variants for the lints about variant names to trigger /// The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold: u64 = 3), (enum_variant_name_threshold: u64 = 3),
/// Lint: STRUCT_VARIANT_NAMES.
///
/// The minimum number of struct fields for the lints about field names to trigger
(struct_field_name_threshold: u64 = 3),
/// Lint: LARGE_ENUM_VARIANT. /// Lint: LARGE_ENUM_VARIANT.
/// ///
/// The maximum size of an enum's variant to avoid box suggestion /// The maximum size of an enum's variant to avoid box suggestion
@ -641,15 +619,8 @@ pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
} }
} }
/// Read the `toml` configuration file. fn deserialize(file: &SourceFile) -> TryConf {
/// match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) {
/// In case of error, the function tries to continue as much as possible.
pub fn read(sess: &Session, path: &Path) -> TryConf {
let file = match sess.source_map().load_file(path) {
Err(e) => return e.into(),
Ok(file) => file,
};
match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(&file)) {
Ok(mut conf) => { Ok(mut conf) => {
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
@ -662,7 +633,7 @@ pub fn read(sess: &Session, path: &Path) -> TryConf {
conf conf
}, },
Err(e) => TryConf::from_toml_error(&file, &e), Err(e) => TryConf::from_toml_error(file, &e),
} }
} }
@ -672,6 +643,60 @@ fn extend_vec_if_indicator_present(vec: &mut Vec<String>, default: &[&str]) {
} }
} }
impl Conf {
pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
static CONF: OnceLock<Conf> = OnceLock::new();
CONF.get_or_init(|| Conf::read_inner(sess, path))
}
fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
match path {
Ok((_, warnings)) => {
for warning in warnings {
sess.warn(warning.clone());
}
},
Err(error) => {
sess.err(format!("error finding Clippy's configuration file: {error}"));
},
}
let TryConf {
mut conf,
errors,
warnings,
} = match path {
Ok((Some(path), _)) => match sess.source_map().load_file(path) {
Ok(file) => deserialize(&file),
Err(error) => {
sess.err(format!("failed to read `{}`: {error}", path.display()));
TryConf::default()
},
},
_ => TryConf::default(),
};
conf.msrv.read_cargo(sess);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.span_err(
error.span,
format!("error reading Clippy's configuration file: {}", error.message),
);
}
for warning in warnings {
sess.span_warn(
warning.span,
format!("error reading Clippy's configuration file: {}", warning.message),
);
}
conf
}
}
const SEPARATOR_WIDTH: usize = 4; const SEPARATOR_WIDTH: usize = 4;
#[derive(Debug)] #[derive(Debug)]

View file

@ -30,7 +30,7 @@ impl<'tcx> LateLintPass<'tcx> for IfChainStyle {
if_chain_local_span(cx, local, if_chain_span), if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be above the `if_chain!`", "`let` expression should be above the `if_chain!`",
); );
} else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) { } else if local.span.eq_ctxt(block.span) && is_if_chain_then(after, block.expr, if_chain_span) {
span_lint( span_lint(
cx, cx,
IF_CHAIN_STYLE, IF_CHAIN_STYLE,

View file

@ -13,6 +13,7 @@ use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::ConstValue; use rustc_middle::mir::ConstValue;
use rustc_middle::ty; use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use std::borrow::Cow; use std::borrow::Cow;
@ -160,12 +161,8 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol {
impl InterningDefinedSymbol { impl InterningDefinedSymbol {
fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> { fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> {
static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR, &paths::TO_STRING_METHOD]; static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR];
static SYMBOL_STR_PATHS: &[&[&str]] = &[ static SYMBOL_STR_PATHS: &[&[&str]] = &[&paths::SYMBOL_AS_STR, &paths::SYMBOL_TO_IDENT_STRING];
&paths::SYMBOL_AS_STR,
&paths::SYMBOL_TO_IDENT_STRING,
&paths::TO_STRING_METHOD,
];
let call = if_chain! { let call = if_chain! {
if let ExprKind::AddrOf(_, _, e) = expr.kind; if let ExprKind::AddrOf(_, _, e) = expr.kind;
if let ExprKind::Unary(UnOp::Deref, e) = e.kind; if let ExprKind::Unary(UnOp::Deref, e) = e.kind;
@ -186,9 +183,19 @@ impl InterningDefinedSymbol {
}; };
// ...which converts it to a string // ...which converts it to a string
let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS }; let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS };
if let Some(path) = paths.iter().find(|path| match_def_path(cx, did, path)); if let Some(is_to_owned) = paths
.iter()
.find_map(|path| if match_def_path(cx, did, path) {
Some(path == &paths::SYMBOL_TO_IDENT_STRING)
} else {
None
})
.or_else(|| if cx.tcx.is_diagnostic_item(sym::to_string_method, did) {
Some(true)
} else {
None
});
then { then {
let is_to_owned = path.last().unwrap().ends_with("string");
return Some(SymbolStrExpr::Expr { return Some(SymbolStrExpr::Expr {
item, item,
is_ident, is_ident,

View file

@ -9,6 +9,7 @@ arrayvec = { version = "0.7", default-features = false }
if_chain = "1.0" if_chain = "1.0"
itertools = "0.10.1" itertools = "0.10.1"
rustc-semver = "1.1" rustc-semver = "1.1"
serde = { version = "1.0" }
[features] [features]
deny-warnings = [] deny-warnings = []

View file

@ -504,7 +504,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> {
}, },
(Some(Constant::Vec(vec)), _) => { (Some(Constant::Vec(vec)), _) => {
if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) { if !vec.is_empty() && vec.iter().all(|x| *x == vec[0]) {
match vec.get(0) { match vec.first() {
Some(Constant::F32(x)) => Some(Constant::F32(*x)), Some(Constant::F32(x)) => Some(Constant::F32(*x)),
Some(Constant::F64(x)) => Some(Constant::F64(*x)), Some(Constant::F64(x)) => Some(Constant::F64(*x)),
_ => None, _ => None,

View file

@ -449,7 +449,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -
} else if name.ident.name == symbol::kw::Default { } else if name.ident.name == symbol::kw::Default {
return Some(VecInitKind::Default); return Some(VecInitKind::Default);
} else if name.ident.name.as_str() == "with_capacity" { } else if name.ident.name.as_str() == "with_capacity" {
let arg = args.get(0)?; let arg = args.first()?;
return match constant_simple(cx, cx.typeck_results(), arg) { return match constant_simple(cx, cx.typeck_results(), arg) {
Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)), Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
_ => Some(VecInitKind::WithExprCapacity(arg.hir_id)), _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),

View file

@ -2027,48 +2027,88 @@ pub fn is_must_use_func_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use)) did.map_or(false, |did| cx.tcx.has_attr(did, sym::must_use))
} }
/// Checks if an expression represents the identity function /// Checks if a function's body represents the identity function. Looks for bodies of the form:
/// Only examines closures and `std::convert::identity` /// * `|x| x`
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// * `|x| return x`
/// Checks if a function's body represents the identity function. Looks for bodies of the form: /// * `|x| { return x }`
/// * `|x| x` /// * `|x| { return x; }`
/// * `|x| return x` ///
/// * `|x| { return x }` /// Consider calling [`is_expr_untyped_identity_function`] or [`is_expr_identity_function`] instead.
/// * `|x| { return x; }` fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool {
fn is_body_identity_function(cx: &LateContext<'_>, func: &Body<'_>) -> bool { let id = if_chain! {
let id = if_chain! { if let [param] = func.params;
if let [param] = func.params; if let PatKind::Binding(_, id, _, _) = param.pat.kind;
if let PatKind::Binding(_, id, _, _) = param.pat.kind; then {
then { id
id } else {
} else { return false;
return false; }
} };
};
let mut expr = func.value; let mut expr = func.value;
loop { loop {
match expr.kind { match expr.kind {
#[rustfmt::skip] ExprKind::Block(
ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _, ) &Block {
| ExprKind::Ret(Some(e)) => expr = e, stmts: [],
#[rustfmt::skip] expr: Some(e),
ExprKind::Block(&Block { stmts: [stmt], expr: None, .. }, _) => { ..
if_chain! {
if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
if let ExprKind::Ret(Some(ret_val)) = e.kind;
then {
expr = ret_val;
} else {
return false;
}
}
}, },
_ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(), _,
} )
| ExprKind::Ret(Some(e)) => expr = e,
ExprKind::Block(
&Block {
stmts: [stmt],
expr: None,
..
},
_,
) => {
if_chain! {
if let StmtKind::Semi(e) | StmtKind::Expr(e) = stmt.kind;
if let ExprKind::Ret(Some(ret_val)) = e.kind;
then {
expr = ret_val;
} else {
return false;
}
}
},
_ => return path_to_local_id(expr, id) && cx.typeck_results().expr_adjustments(expr).is_empty(),
} }
} }
}
/// This is the same as [`is_expr_identity_function`], but does not consider closures
/// with type annotations for its bindings (or similar) as identity functions:
/// * `|x: u8| x`
/// * `std::convert::identity::<u8>`
pub fn is_expr_untyped_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Closure(&Closure { body, fn_decl, .. })
if fn_decl.inputs.iter().all(|ty| matches!(ty.kind, TyKind::Infer)) =>
{
is_body_identity_function(cx, cx.tcx.hir().body(body))
},
ExprKind::Path(QPath::Resolved(_, path))
if path.segments.iter().all(|seg| seg.infer_args)
&& let Some(did) = path.res.opt_def_id() => {
cx.tcx.is_diagnostic_item(sym::convert_identity, did)
},
_ => false,
}
}
/// Checks if an expression represents the identity function
/// Only examines closures and `std::convert::identity`
///
/// NOTE: If you want to use this function to find out if a closure is unnecessary, you likely want
/// to call [`is_expr_untyped_identity_function`] instead, which makes sure that the closure doesn't
/// have type annotations. This is important because removing a closure with bindings can
/// remove type information that helped type inference before, which can then lead to compile
/// errors.
pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
match expr.kind { match expr.kind {
ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)), ExprKind::Closure(&Closure { body, .. }) => is_body_identity_function(cx, cx.tcx.hir().body(body)),
_ => path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)), _ => path_def_id(cx, expr).map_or(false, |id| cx.tcx.is_diagnostic_item(sym::convert_identity, id)),

View file

@ -1,9 +1,7 @@
use std::sync::OnceLock;
use rustc_ast::Attribute; use rustc_ast::Attribute;
use rustc_semver::RustcVersion; use rustc_semver::RustcVersion;
use rustc_session::Session; use rustc_session::Session;
use rustc_span::Span; use serde::Deserialize;
use crate::attrs::get_unique_attr; use crate::attrs::get_unique_attr;
@ -53,65 +51,45 @@ msrv_aliases! {
1,15,0 { MAYBE_BOUND_IN_WHERE } 1,15,0 { MAYBE_BOUND_IN_WHERE }
} }
fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
return Some(version);
} else if let Some(sess) = sess {
if let Some(span) = span {
sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
}
}
None
}
/// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]`
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone)]
pub struct Msrv { pub struct Msrv {
stack: Vec<RustcVersion>, stack: Vec<RustcVersion>,
} }
impl<'de> Deserialize<'de> for Msrv {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let v = String::deserialize(deserializer)?;
RustcVersion::parse(&v)
.map(|v| Msrv { stack: vec![v] })
.map_err(|_| serde::de::Error::custom("not a valid Rust version"))
}
}
impl Msrv { impl Msrv {
fn new(initial: Option<RustcVersion>) -> Self { pub fn empty() -> Msrv {
Self { Msrv { stack: Vec::new() }
stack: Vec::from_iter(initial),
}
} }
fn read_inner(conf_msrv: &Option<String>, sess: &Session) -> Self { pub fn read_cargo(&mut self, sess: &Session) {
let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION") let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION")
.ok() .ok()
.and_then(|v| parse_msrv(&v, None, None)); .and_then(|v| RustcVersion::parse(&v).ok());
let clippy_msrv = conf_msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(format!(
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
));
None
})
});
// if both files have an msrv, let's compare them and emit a warning if they differ match (self.current(), cargo_msrv) {
if let Some(cargo_msrv) = cargo_msrv (None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv],
&& let Some(clippy_msrv) = clippy_msrv (Some(clippy_msrv), Some(cargo_msrv)) => {
&& clippy_msrv != cargo_msrv if clippy_msrv != cargo_msrv {
{ sess.warn(format!(
sess.warn(format!( "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`" ));
)); }
},
_ => {},
} }
Self::new(clippy_msrv.or(cargo_msrv))
}
/// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version`
/// field in `Cargo.toml`
///
/// Returns a `&'static Msrv` as `Copy` types are more easily passed to the
/// `register_{late,early}_pass` callbacks
pub fn read(conf_msrv: &Option<String>, sess: &Session) -> &'static Self {
static PARSED: OnceLock<Msrv> = OnceLock::new();
PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
} }
pub fn current(&self) -> Option<RustcVersion> { pub fn current(&self) -> Option<RustcVersion> {
@ -125,10 +103,14 @@ impl Msrv {
fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> { fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") { if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") {
if let Some(msrv) = msrv_attr.value_str() { if let Some(msrv) = msrv_attr.value_str() {
return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span)); if let Ok(version) = RustcVersion::parse(msrv.as_str()) {
} return Some(version);
}
sess.span_err(msrv_attr.span, "bad clippy attribute"); sess.span_err(msrv_attr.span, format!("`{msrv}` is not a valid Rust version"));
} else {
sess.span_err(msrv_attr.span, "bad clippy attribute");
}
} }
None None

View file

@ -98,7 +98,6 @@ pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol",
pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"]; pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"];
#[cfg(feature = "internal")] #[cfg(feature = "internal")]
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]; pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"]; pub const TOKIO_IO_ASYNCREADEXT: [&str; 5] = ["tokio", "io", "util", "async_read_ext", "AsyncReadExt"];
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates

View file

@ -236,6 +236,59 @@ pub fn count_match_end(str1: &str, str2: &str) -> StrCount {
}) })
} }
/// Returns a `snake_case` version of the input
/// ```
/// use clippy_utils::str_utils::to_snake_case;
/// assert_eq!(to_snake_case("AbcDef"), "abc_def");
/// assert_eq!(to_snake_case("ABCD"), "a_b_c_d");
/// assert_eq!(to_snake_case("AbcDD"), "abc_d_d");
/// assert_eq!(to_snake_case("Abc1DD"), "abc1_d_d");
/// ```
pub fn to_snake_case(name: &str) -> String {
let mut s = String::new();
for (i, c) in name.chars().enumerate() {
if c.is_uppercase() {
// characters without capitalization are considered lowercase
if i != 0 {
s.push('_');
}
s.extend(c.to_lowercase());
} else {
s.push(c);
}
}
s
}
/// Returns a `CamelCase` version of the input
/// ```
/// use clippy_utils::str_utils::to_camel_case;
/// assert_eq!(to_camel_case("abc_def"), "AbcDef");
/// assert_eq!(to_camel_case("a_b_c_d"), "ABCD");
/// assert_eq!(to_camel_case("abc_d_d"), "AbcDD");
/// assert_eq!(to_camel_case("abc1_d_d"), "Abc1DD");
/// ```
pub fn to_camel_case(item_name: &str) -> String {
let mut s = String::new();
let mut up = true;
for c in item_name.chars() {
if c.is_uppercase() {
// we only turn snake case text into CamelCase
return item_name.to_string();
}
if c == '_' {
up = true;
continue;
}
if up {
up = false;
s.extend(c.to_uppercase());
} else {
s.push(c);
}
}
s
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2023-10-06" channel = "nightly-2023-10-21"
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

@ -147,9 +147,9 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
(previous)(sess, lint_store); (previous)(sess, lint_store);
} }
let conf = clippy_lints::read_conf(sess, &conf_path); let conf = clippy_lints::Conf::read(sess, &conf_path);
clippy_lints::register_plugins(lint_store, sess, &conf); clippy_lints::register_plugins(lint_store, sess, conf);
clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf); clippy_lints::register_pre_expansion_lints(lint_store, conf);
clippy_lints::register_renamed(lint_store); clippy_lints::register_renamed(lint_store);
})); }));

View file

@ -105,27 +105,20 @@ static EXTERN_FLAGS: LazyLock<Vec<String>> = LazyLock::new(|| {
// whether to run internal tests or not // whether to run internal tests or not
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal"); const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal");
fn canonicalize(path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
fs::create_dir_all(path).unwrap();
fs::canonicalize(path).unwrap_or_else(|err| panic!("{} cannot be canonicalized: {err}", path.display()))
}
fn base_config(test_dir: &str) -> (Config, Args) { fn base_config(test_dir: &str) -> (Config, Args) {
let mut args = Args::test().unwrap(); let mut args = Args::test().unwrap();
args.bless |= var_os("RUSTC_BLESS").is_some_and(|v| v != "0"); args.bless |= var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
let target_dir = PathBuf::from(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into()));
let mut config = Config { let mut config = Config {
mode: Mode::Yolo { mode: Mode::Yolo {
rustfix: ui_test::RustfixMode::Everything, rustfix: ui_test::RustfixMode::Everything,
}, },
stderr_filters: vec![(Match::PathBackslash, b"/")],
stdout_filters: vec![],
filter_files: env::var("TESTNAME") filter_files: env::var("TESTNAME")
.map(|filters| filters.split(',').map(str::to_string).collect()) .map(|filters| filters.split(',').map(str::to_string).collect())
.unwrap_or_default(), .unwrap_or_default(),
target: None, target: None,
out_dir: canonicalize(var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into())).join("ui_test"), out_dir: target_dir.join("ui_test"),
..Config::rustc(Path::new("tests").join(test_dir)) ..Config::rustc(Path::new("tests").join(test_dir))
}; };
config.with_args(&args, /* bless by default */ false); config.with_args(&args, /* bless by default */ false);
@ -168,19 +161,13 @@ fn run_ui() {
config config
.program .program
.envs .envs
.push(("CLIPPY_CONF_DIR".into(), Some(canonicalize("tests").into()))); .push(("CLIPPY_CONF_DIR".into(), Some("tests".into())));
let quiet = args.quiet;
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
ui_test::default_file_filter, ui_test::default_file_filter,
ui_test::default_per_file_config, ui_test::default_per_file_config,
if quiet { status_emitter::Text::from(args.format),
status_emitter::Text::quiet()
} else {
status_emitter::Text::verbose()
},
) )
.unwrap(); .unwrap();
} }
@ -194,17 +181,12 @@ fn run_internal_tests() {
if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling { if let OutputConflictHandling::Error(err) = &mut config.output_conflict_handling {
*err = "cargo uitest --features internal -- -- --bless".into(); *err = "cargo uitest --features internal -- -- --bless".into();
} }
let quiet = args.quiet;
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
ui_test::default_file_filter, ui_test::default_file_filter,
ui_test::default_per_file_config, ui_test::default_per_file_config,
if quiet { status_emitter::Text::from(args.format),
status_emitter::Text::quiet()
} else {
status_emitter::Text::verbose()
},
) )
.unwrap(); .unwrap();
} }
@ -212,22 +194,9 @@ fn run_internal_tests() {
fn run_ui_toml() { fn run_ui_toml() {
let (mut config, args) = base_config("ui-toml"); let (mut config, args) = base_config("ui-toml");
config.stderr_filters = vec![ config
( .stderr_filters
Match::Exact( .push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR"));
canonicalize("tests")
.parent()
.unwrap()
.to_string_lossy()
.as_bytes()
.to_vec(),
),
b"$DIR",
),
(Match::Exact(b"\\".to_vec()), b"/"),
];
let quiet = args.quiet;
ui_test::run_tests_generic( ui_test::run_tests_generic(
vec![config], vec![config],
@ -238,11 +207,7 @@ fn run_ui_toml() {
.envs .envs
.push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into()))); .push(("CLIPPY_CONF_DIR".into(), Some(path.parent().unwrap().into())));
}, },
if quiet { status_emitter::Text::from(args.format),
status_emitter::Text::quiet()
} else {
status_emitter::Text::verbose()
},
) )
.unwrap(); .unwrap();
} }
@ -270,22 +235,9 @@ fn run_ui_cargo() {
}); });
config.edition = None; config.edition = None;
config.stderr_filters = vec![ config
( .stderr_filters
Match::Exact( .push((Match::from(env::current_dir().unwrap().as_path()), b"$DIR"));
canonicalize("tests")
.parent()
.unwrap()
.to_string_lossy()
.as_bytes()
.to_vec(),
),
b"$DIR",
),
(Match::Exact(b"\\".to_vec()), b"/"),
];
let quiet = args.quiet;
let ignored_32bit = |path: &Path| { let ignored_32bit = |path: &Path| {
// FIXME: for some reason the modules are linted in a different order for this test // FIXME: for some reason the modules are linted in a different order for this test
@ -297,20 +249,8 @@ fn run_ui_cargo() {
|path, config| { |path, config| {
path.ends_with("Cargo.toml") && ui_test::default_any_file_filter(path, config) && !ignored_32bit(path) path.ends_with("Cargo.toml") && ui_test::default_any_file_filter(path, config) && !ignored_32bit(path)
}, },
|config, path, _file_contents| { |_config, _path, _file_contents| {},
config.out_dir = canonicalize( status_emitter::Text::from(args.format),
std::env::current_dir()
.unwrap()
.join("target")
.join("ui_test_cargo/")
.join(path.parent().unwrap()),
);
},
if quiet {
status_emitter::Text::quiet()
} else {
status_emitter::Text::verbose()
},
) )
.unwrap(); .unwrap();
} }

View file

@ -12,5 +12,5 @@ fn main() {
const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"]; const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
// Don't lint, not a diagnostic or language item // Don't lint, not a diagnostic or language item
const OPS_MOD: [&str; 5] = ["core", "ops"]; const OPS_MOD: [&str; 2] = ["core", "ops"];
} }

View file

@ -0,0 +1 @@
ignore-interior-mutability = ["borrow_interior_mutable_const_ignore::Counted"]

View file

@ -0,0 +1,37 @@
//@compile-flags: --crate-name borrow_interior_mutable_const_ignore
#![warn(clippy::borrow_interior_mutable_const)]
#![allow(clippy::declare_interior_mutable_const)]
use core::cell::Cell;
use std::cmp::{Eq, PartialEq};
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Counted<T> {
count: AtomicUsize,
val: T,
}
impl<T> Counted<T> {
const fn new(val: T) -> Self {
Self {
count: AtomicUsize::new(0),
val,
}
}
}
enum OptionalCell {
Unfrozen(Counted<bool>),
Frozen,
}
const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Counted::new(true));
const FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen;
fn main() {
let _ = &UNFROZEN_VARIANT;
}

View file

@ -0,0 +1 @@
ignore-interior-mutability = ["declare_interior_mutable_const_ignore::Counted"]

View file

@ -0,0 +1,46 @@
//@compile-flags: --crate-name declare_interior_mutable_const_ignore
#![warn(clippy::declare_interior_mutable_const)]
#![allow(clippy::borrow_interior_mutable_const)]
use core::cell::Cell;
use std::cmp::{Eq, PartialEq};
use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Counted<T> {
count: AtomicUsize,
val: T,
}
impl<T> Counted<T> {
const fn new(val: T) -> Self {
Self {
count: AtomicUsize::new(0),
val,
}
}
}
enum OptionalCell {
Unfrozen(Counted<bool>),
Frozen,
}
const UNFROZEN_VARIANT: OptionalCell = OptionalCell::Unfrozen(Counted::new(true));
const FROZEN_VARIANT: OptionalCell = OptionalCell::Frozen;
const fn unfrozen_variant() -> OptionalCell {
OptionalCell::Unfrozen(Counted::new(true))
}
const fn frozen_variant() -> OptionalCell {
OptionalCell::Frozen
}
const UNFROZEN_VARIANT_FROM_FN: OptionalCell = unfrozen_variant();
const FROZEN_VARIANT_FROM_FN: OptionalCell = frozen_variant();
fn main() {}

View file

@ -1,16 +0,0 @@
enum Foo {
AFoo,
BFoo,
CFoo,
DFoo,
}
enum Foo2 {
//~^ ERROR: all variants have the same postfix
AFoo,
BFoo,
CFoo,
DFoo,
EFoo,
}
fn main() {}

View file

@ -1,18 +0,0 @@
error: all variants have the same postfix: `Foo`
--> $DIR/enum_variant_names.rs:7:1
|
LL | / enum Foo2 {
LL | |
LL | | AFoo,
LL | | BFoo,
... |
LL | | EFoo,
LL | | }
| |_^
|
= help: remove the postfixes and use full paths to the variants instead of glob imports
= note: `-D clippy::enum-variant-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::enum_variant_names)]`
error: aborting due to previous error

View file

@ -0,0 +1 @@
avoid-breaking-exported-api = false

View file

@ -0,0 +1,16 @@
//! As avoid-breaking-exported-api is `false`, nothing here should lint
#![warn(clippy::impl_trait_in_params)]
#![no_main]
//@no-rustfix
pub trait Trait {}
trait Private {
fn t(_: impl Trait);
fn tt<T: Trait>(_: T);
}
pub trait Public {
fn t(_: impl Trait); //~ ERROR: `impl Trait` used as a function parameter
fn tt<T: Trait>(_: T);
}

View file

@ -0,0 +1,15 @@
error: `impl Trait` used as a function parameter
--> $DIR/impl_trait_in_params.rs:14:13
|
LL | fn t(_: impl Trait);
| ^^^^^^^^^^
|
= note: `-D clippy::impl-trait-in-params` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::impl_trait_in_params)]`
help: add a type parameter
|
LL | fn t<{ /* Generic name */ }: Trait>(_: impl Trait);
| +++++++++++++++++++++++++++++++
error: aborting due to previous error

View file

@ -1,4 +1,4 @@
//@error-in-other-file: `invalid.version` is not a valid Rust version //@error-in-other-file: not a valid Rust version
#![allow(clippy::redundant_clone)] #![allow(clippy::redundant_clone)]

View file

@ -1,4 +1,8 @@
error: error reading Clippy's configuration file. `invalid.version` is not a valid Rust version error: error reading Clippy's configuration file: not a valid Rust version
--> $DIR/$DIR/clippy.toml:1:8
|
LL | msrv = "invalid.version"
| ^^^^^^^^^^^^^^^^^
error: aborting due to previous error error: aborting due to previous error

View file

@ -1 +1,2 @@
struct-field-name-threshold = 0
enum-variant-name-threshold = 0 enum-variant-name-threshold = 0

View file

@ -1,3 +1,5 @@
struct Data {}
enum Actions {} enum Actions {}
fn main() {} fn main() {}

View file

@ -1 +1,2 @@
enum-variant-name-threshold = 5 enum-variant-name-threshold = 5
struct-field-name-threshold = 5

View file

@ -0,0 +1,32 @@
#![warn(clippy::struct_field_names)]
struct Data {
a_data: u8,
b_data: u8,
c_data: u8,
d_data: u8,
}
struct Data2 {
//~^ ERROR: all fields have the same postfix
a_data: u8,
b_data: u8,
c_data: u8,
d_data: u8,
e_data: u8,
}
enum Foo {
AFoo,
BFoo,
CFoo,
DFoo,
}
enum Foo2 {
//~^ ERROR: all variants have the same postfix
AFoo,
BFoo,
CFoo,
DFoo,
EFoo,
}
fn main() {}

View file

@ -0,0 +1,34 @@
error: all fields have the same postfix: `data`
--> $DIR/item_name_repetitions.rs:9:1
|
LL | / struct Data2 {
LL | |
LL | | a_data: u8,
LL | | b_data: u8,
... |
LL | | e_data: u8,
LL | | }
| |_^
|
= help: remove the postfixes
= note: `-D clippy::struct-field-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::struct_field_names)]`
error: all variants have the same postfix: `Foo`
--> $DIR/item_name_repetitions.rs:23:1
|
LL | / enum Foo2 {
LL | |
LL | | AFoo,
LL | | BFoo,
... |
LL | | EFoo,
LL | | }
| |_^
|
= help: remove the postfixes and use full paths to the variants instead of glob imports
= note: `-D clippy::enum-variant-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::enum_variant_names)]`
error: aborting due to 2 previous errors

View file

@ -1,5 +1,6 @@
//! this is crate //! this is crate
#![allow(missing_docs)] #![allow(missing_docs)]
#![allow(clippy::struct_field_names)]
#![warn(clippy::missing_docs_in_private_items)] #![warn(clippy::missing_docs_in_private_items)]
/// this is mod /// this is mod

View file

@ -1,5 +1,5 @@
error: missing documentation for a function error: missing documentation for a function
--> $DIR/pub_crate_missing_doc.rs:12:5 --> $DIR/pub_crate_missing_doc.rs:13:5
| |
LL | pub(crate) fn crate_no_docs() {} LL | pub(crate) fn crate_no_docs() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8,25 +8,25 @@ LL | pub(crate) fn crate_no_docs() {}
= help: to override `-D warnings` add `#[allow(clippy::missing_docs_in_private_items)]` = help: to override `-D warnings` add `#[allow(clippy::missing_docs_in_private_items)]`
error: missing documentation for a function error: missing documentation for a function
--> $DIR/pub_crate_missing_doc.rs:15:5 --> $DIR/pub_crate_missing_doc.rs:16:5
| |
LL | pub(super) fn super_no_docs() {} LL | pub(super) fn super_no_docs() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: missing documentation for a function error: missing documentation for a function
--> $DIR/pub_crate_missing_doc.rs:23:9 --> $DIR/pub_crate_missing_doc.rs:24:9
| |
LL | pub(crate) fn sub_crate_no_docs() {} LL | pub(crate) fn sub_crate_no_docs() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: missing documentation for a struct field error: missing documentation for a struct field
--> $DIR/pub_crate_missing_doc.rs:33:9 --> $DIR/pub_crate_missing_doc.rs:34:9
| |
LL | pub(crate) crate_field_no_docs: (), LL | pub(crate) crate_field_no_docs: (),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: missing documentation for a struct error: missing documentation for a struct
--> $DIR/pub_crate_missing_doc.rs:39:5 --> $DIR/pub_crate_missing_doc.rs:40:5
| |
LL | / pub(crate) struct CrateStructNoDocs { LL | / pub(crate) struct CrateStructNoDocs {
LL | | /// some docs LL | | /// some docs
@ -38,13 +38,13 @@ LL | | }
| |_____^ | |_____^
error: missing documentation for a struct field error: missing documentation for a struct field
--> $DIR/pub_crate_missing_doc.rs:42:9 --> $DIR/pub_crate_missing_doc.rs:43:9
| |
LL | pub(crate) crate_field_no_docs: (), LL | pub(crate) crate_field_no_docs: (),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: missing documentation for a type alias error: missing documentation for a type alias
--> $DIR/pub_crate_missing_doc.rs:51:1 --> $DIR/pub_crate_missing_doc.rs:52:1
| |
LL | type CrateTypedefNoDocs = String; LL | type CrateTypedefNoDocs = String;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -53,6 +53,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
single-char-binding-names-threshold single-char-binding-names-threshold
stack-size-threshold stack-size-threshold
standard-macro-braces standard-macro-braces
struct-field-name-threshold
suppress-restriction-lint-in-const suppress-restriction-lint-in-const
third-party third-party
too-large-for-stack too-large-for-stack
@ -126,6 +127,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
single-char-binding-names-threshold single-char-binding-names-threshold
stack-size-threshold stack-size-threshold
standard-macro-braces standard-macro-braces
struct-field-name-threshold
suppress-restriction-lint-in-const suppress-restriction-lint-in-const
third-party third-party
too-large-for-stack too-large-for-stack

View file

@ -0,0 +1,5 @@
fn main() {
#[clippy::author]
let print_text = |x| println!("{}", x);
print_text("hello");
}

View file

@ -0,0 +1,39 @@
if let StmtKind::Local(local) = stmt.kind
&& let Some(init) = local.init
&& let ExprKind::Closure(CaptureBy::Ref, fn_decl, body_id, _, None) = init.kind
&& let FnRetTy::DefaultReturn(_) = fn_decl.output
&& expr = &cx.tcx.hir().body(body_id).value
&& let ExprKind::Block(block, None) = expr.kind
&& block.stmts.len() == 1
&& let StmtKind::Semi(e) = block.stmts[0].kind
&& let ExprKind::Call(func, args) = e.kind
&& let ExprKind::Path(ref qpath) = func.kind
&& match_qpath(qpath, &["$crate", "io", "_print"])
&& args.len() == 1
&& let ExprKind::Call(func1, args1) = args[0].kind
&& let ExprKind::Path(ref qpath1) = func1.kind
&& args1.len() == 2
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind
&& let ExprKind::Array(elements) = inner.kind
&& elements.len() == 2
&& let ExprKind::Lit(ref lit) = elements[0].kind
&& let LitKind::Str(s, _) = lit.node
&& s.as_str() == ""
&& let ExprKind::Lit(ref lit1) = elements[1].kind
&& let LitKind::Str(s1, _) = lit1.node
&& s1.as_str() == "\n"
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args1[1].kind
&& let ExprKind::Array(elements1) = inner1.kind
&& elements1.len() == 1
&& let ExprKind::Call(func2, args2) = elements1[0].kind
&& let ExprKind::Path(ref qpath2) = func2.kind
&& args2.len() == 1
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind
&& let ExprKind::Path(ref qpath3) = inner2.kind
&& match_qpath(qpath3, &["x"])
&& block.expr.is_none()
&& let PatKind::Binding(BindingAnnotation::NONE, _, name, None) = local.pat.kind
&& name.as_str() == "print_text"
{
// report your lint here
}

View file

@ -0,0 +1,8 @@
#![feature(stmt_expr_attributes)]
fn main() {
#[clippy::author]
for i in 0..1 {
println!("{}", i);
}
}

View file

@ -0,0 +1,48 @@
if let Some(higher::ForLoop { pat: pat, arg: arg, body: body, .. }) = higher::ForLoop::hir(expr)
&& let PatKind::Binding(BindingAnnotation::NONE, _, name, None) = pat.kind
&& name.as_str() == "i"
&& let ExprKind::Struct(qpath, fields, None) = arg.kind
&& matches!(qpath, QPath::LangItem(LangItem::Range, _))
&& fields.len() == 2
&& fields[0].ident.as_str() == "start"
&& let ExprKind::Lit(ref lit) = fields[0].expr.kind
&& let LitKind::Int(0, LitIntType::Unsuffixed) = lit.node
&& fields[1].ident.as_str() == "end"
&& let ExprKind::Lit(ref lit1) = fields[1].expr.kind
&& let LitKind::Int(1, LitIntType::Unsuffixed) = lit1.node
&& let ExprKind::Block(block, None) = body.kind
&& block.stmts.len() == 1
&& let StmtKind::Semi(e) = block.stmts[0].kind
&& let ExprKind::Block(block1, None) = e.kind
&& block1.stmts.len() == 1
&& let StmtKind::Semi(e1) = block1.stmts[0].kind
&& let ExprKind::Call(func, args) = e1.kind
&& let ExprKind::Path(ref qpath1) = func.kind
&& match_qpath(qpath1, &["$crate", "io", "_print"])
&& args.len() == 1
&& let ExprKind::Call(func1, args1) = args[0].kind
&& let ExprKind::Path(ref qpath2) = func1.kind
&& args1.len() == 2
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = args1[0].kind
&& let ExprKind::Array(elements) = inner.kind
&& elements.len() == 2
&& let ExprKind::Lit(ref lit2) = elements[0].kind
&& let LitKind::Str(s, _) = lit2.node
&& s.as_str() == ""
&& let ExprKind::Lit(ref lit3) = elements[1].kind
&& let LitKind::Str(s1, _) = lit3.node
&& s1.as_str() == "\n"
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner1) = args1[1].kind
&& let ExprKind::Array(elements1) = inner1.kind
&& elements1.len() == 1
&& let ExprKind::Call(func2, args2) = elements1[0].kind
&& let ExprKind::Path(ref qpath3) = func2.kind
&& args2.len() == 1
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner2) = args2[0].kind
&& let ExprKind::Path(ref qpath4) = inner2.kind
&& match_qpath(qpath4, &["i"])
&& block1.expr.is_none()
&& block.expr.is_none()
{
// report your lint here
}

View file

@ -14,17 +14,20 @@ impl Bar {
fn main() { fn main() {
let x = vec![2, 3, 5]; let x = vec![2, 3, 5];
let _ = x.first(); // Use x.first() let _ = x.first();
//~^ ERROR: accessing first element with `x.get(0)`
let _ = x.get(1); let _ = x.get(1);
let _ = x[0]; let _ = x[0];
let y = [2, 3, 5]; let y = [2, 3, 5];
let _ = y.first(); // Use y.first() let _ = y.first();
//~^ ERROR: accessing first element with `y.get(0)`
let _ = y.get(1); let _ = y.get(1);
let _ = y[0]; let _ = y[0];
let z = &[2, 3, 5]; let z = &[2, 3, 5];
let _ = z.first(); // Use z.first() let _ = z.first();
//~^ ERROR: accessing first element with `z.get(0)`
let _ = z.get(1); let _ = z.get(1);
let _ = z[0]; let _ = z[0];
@ -37,4 +40,8 @@ fn main() {
let bar = Bar { arr: [0, 1, 2] }; let bar = Bar { arr: [0, 1, 2] };
let _ = bar.get(0); // Do not lint, because Bar is struct. let _ = bar.get(0); // Do not lint, because Bar is struct.
let non_primitives = [vec![1, 2], vec![3, 4]];
let _ = non_primitives.first();
//~^ ERROR: accessing first element with `non_primitives.get(0)`
} }

View file

@ -14,17 +14,20 @@ impl Bar {
fn main() { fn main() {
let x = vec![2, 3, 5]; let x = vec![2, 3, 5];
let _ = x.get(0); // Use x.first() let _ = x.get(0);
//~^ ERROR: accessing first element with `x.get(0)`
let _ = x.get(1); let _ = x.get(1);
let _ = x[0]; let _ = x[0];
let y = [2, 3, 5]; let y = [2, 3, 5];
let _ = y.get(0); // Use y.first() let _ = y.get(0);
//~^ ERROR: accessing first element with `y.get(0)`
let _ = y.get(1); let _ = y.get(1);
let _ = y[0]; let _ = y[0];
let z = &[2, 3, 5]; let z = &[2, 3, 5];
let _ = z.get(0); // Use z.first() let _ = z.get(0);
//~^ ERROR: accessing first element with `z.get(0)`
let _ = z.get(1); let _ = z.get(1);
let _ = z[0]; let _ = z[0];
@ -37,4 +40,8 @@ fn main() {
let bar = Bar { arr: [0, 1, 2] }; let bar = Bar { arr: [0, 1, 2] };
let _ = bar.get(0); // Do not lint, because Bar is struct. let _ = bar.get(0); // Do not lint, because Bar is struct.
let non_primitives = [vec![1, 2], vec![3, 4]];
let _ = non_primitives.get(0);
//~^ ERROR: accessing first element with `non_primitives.get(0)`
} }

View file

@ -1,23 +1,29 @@
error: accessing first element with `x.get(0)` error: accessing first element with `x.get(0)`
--> $DIR/get_first.rs:17:13 --> $DIR/get_first.rs:17:13
| |
LL | let _ = x.get(0); // Use x.first() LL | let _ = x.get(0);
| ^^^^^^^^ help: try: `x.first()` | ^^^^^^^^ help: try: `x.first()`
| |
= note: `-D clippy::get-first` implied by `-D warnings` = note: `-D clippy::get-first` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::get_first)]` = help: to override `-D warnings` add `#[allow(clippy::get_first)]`
error: accessing first element with `y.get(0)` error: accessing first element with `y.get(0)`
--> $DIR/get_first.rs:22:13 --> $DIR/get_first.rs:23:13
| |
LL | let _ = y.get(0); // Use y.first() LL | let _ = y.get(0);
| ^^^^^^^^ help: try: `y.first()` | ^^^^^^^^ help: try: `y.first()`
error: accessing first element with `z.get(0)` error: accessing first element with `z.get(0)`
--> $DIR/get_first.rs:27:13 --> $DIR/get_first.rs:29:13
| |
LL | let _ = z.get(0); // Use z.first() LL | let _ = z.get(0);
| ^^^^^^^^ help: try: `z.first()` | ^^^^^^^^ help: try: `z.first()`
error: aborting due to 3 previous errors error: accessing first element with `non_primitives.get(0)`
--> $DIR/get_first.rs:45:13
|
LL | let _ = non_primitives.get(0);
| ^^^^^^^^^^^^^^^^^^^^^ help: try: `non_primitives.first()`
error: aborting due to 4 previous errors

View file

@ -1,20 +1,47 @@
#![allow(unused)] #![allow(unused)]
#![warn(clippy::impl_trait_in_params)] #![warn(clippy::impl_trait_in_params)]
//@no-rustfix //@no-rustfix
pub trait Trait {} pub trait Trait {}
pub trait AnotherTrait<T> {} pub trait AnotherTrait<T> {}
// Should warn // Should warn
pub fn a(_: impl Trait) {} pub fn a(_: impl Trait) {}
//~^ ERROR: '`impl Trait` used as a function parameter' //~^ ERROR: `impl Trait` used as a function parameter
//~| NOTE: `-D clippy::impl-trait-in-params` implied by `-D warnings`
pub fn c<C: Trait>(_: C, _: impl Trait) {} pub fn c<C: Trait>(_: C, _: impl Trait) {}
//~^ ERROR: '`impl Trait` used as a function parameter' //~^ ERROR: `impl Trait` used as a function parameter
fn d(_: impl AnotherTrait<u32>) {}
// Shouldn't warn // Shouldn't warn
pub fn b<B: Trait>(_: B) {} pub fn b<B: Trait>(_: B) {}
fn e<T: AnotherTrait<u32>>(_: T) {} fn e<T: AnotherTrait<u32>>(_: T) {}
fn d(_: impl AnotherTrait<u32>) {}
//------ IMPLS
pub trait Public {
// See test in ui-toml for a case where avoid-breaking-exported-api is set to false
fn t(_: impl Trait);
fn tt<T: Trait>(_: T) {}
}
trait Private {
// This shouldn't lint
fn t(_: impl Trait);
fn tt<T: Trait>(_: T) {}
}
struct S;
impl S {
pub fn h(_: impl Trait) {} //~ ERROR: `impl Trait` used as a function parameter
fn i(_: impl Trait) {}
pub fn j<J: Trait>(_: J) {}
pub fn k<K: AnotherTrait<u32>>(_: K, _: impl AnotherTrait<u32>) {} //~ ERROR: `impl Trait` used as a function parameter
}
// Trying with traits
impl Public for S {
fn t(_: impl Trait) {}
}
fn main() {} fn main() {}

View file

@ -1,5 +1,5 @@
error: '`impl Trait` used as a function parameter' error: `impl Trait` used as a function parameter
--> $DIR/impl_trait_in_params.rs:8:13 --> $DIR/impl_trait_in_params.rs:9:13
| |
LL | pub fn a(_: impl Trait) {} LL | pub fn a(_: impl Trait) {}
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -11,7 +11,7 @@ help: add a type parameter
LL | pub fn a<{ /* Generic name */ }: Trait>(_: impl Trait) {} LL | pub fn a<{ /* Generic name */ }: Trait>(_: impl Trait) {}
| +++++++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++
error: '`impl Trait` used as a function parameter' error: `impl Trait` used as a function parameter
--> $DIR/impl_trait_in_params.rs:11:29 --> $DIR/impl_trait_in_params.rs:11:29
| |
LL | pub fn c<C: Trait>(_: C, _: impl Trait) {} LL | pub fn c<C: Trait>(_: C, _: impl Trait) {}
@ -22,5 +22,27 @@ help: add a type parameter
LL | pub fn c<C: Trait, { /* Generic name */ }: Trait>(_: C, _: impl Trait) {} LL | pub fn c<C: Trait, { /* Generic name */ }: Trait>(_: C, _: impl Trait) {}
| +++++++++++++++++++++++++++++++ | +++++++++++++++++++++++++++++++
error: aborting due to 2 previous errors error: `impl Trait` used as a function parameter
--> $DIR/impl_trait_in_params.rs:36:17
|
LL | pub fn h(_: impl Trait) {}
| ^^^^^^^^^^
|
help: add a type parameter
|
LL | pub fn h<{ /* Generic name */ }: Trait>(_: impl Trait) {}
| +++++++++++++++++++++++++++++++
error: `impl Trait` used as a function parameter
--> $DIR/impl_trait_in_params.rs:39:45
|
LL | pub fn k<K: AnotherTrait<u32>>(_: K, _: impl AnotherTrait<u32>) {}
| ^^^^^^^^^^^^^^^^^^^^^^
|
help: add a type parameter
|
LL | pub fn k<K: AnotherTrait<u32>, { /* Generic name */ }: AnotherTrait<u32>>(_: K, _: impl AnotherTrait<u32>) {}
| +++++++++++++++++++++++++++++++++++++++++++
error: aborting due to 4 previous errors

View file

@ -122,3 +122,37 @@ fn main() {
} }
} }
} }
fn issue11635() {
// A little more involved than the original repro in the issue, but this tests that it correctly
// works for more than one deref step
use std::ops::Deref;
pub struct Thing(Vec<u8>);
pub struct Thing2(Thing);
impl Deref for Thing {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for Thing2 {
type Target = Thing;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> IntoIterator for &'a Thing2 {
type Item = &'a u8;
type IntoIter = <&'a [u8] as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
}

View file

@ -2,6 +2,7 @@
#![warn(clippy::manual_filter_map)] #![warn(clippy::manual_filter_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure #![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
#![allow(clippy::useless_vec)] #![allow(clippy::useless_vec)]
#![allow(clippy::struct_field_names)]
fn main() { fn main() {
// is_some(), unwrap() // is_some(), unwrap()

View file

@ -2,6 +2,7 @@
#![warn(clippy::manual_filter_map)] #![warn(clippy::manual_filter_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure #![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
#![allow(clippy::useless_vec)] #![allow(clippy::useless_vec)]
#![allow(clippy::struct_field_names)]
fn main() { fn main() {
// is_some(), unwrap() // is_some(), unwrap()

View file

@ -1,11 +1,11 @@
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:8:19 --> $DIR/manual_filter_map.rs:9:19
| |
LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:8:30 --> $DIR/manual_filter_map.rs:9:30
| |
LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -13,31 +13,31 @@ LL | let _ = (0..).filter(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap
= help: to override `-D warnings` add `#[allow(clippy::manual_filter_map)]` = help: to override `-D warnings` add `#[allow(clippy::manual_filter_map)]`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:11:19 --> $DIR/manual_filter_map.rs:12:19
| |
LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_opt(a))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:11:31 --> $DIR/manual_filter_map.rs:12:31
| |
LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); LL | let _ = (0..).filter(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^ | ^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:14:19 --> $DIR/manual_filter_map.rs:15:19
| |
LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:14:31 --> $DIR/manual_filter_map.rs:15:31
| |
LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^ | ^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:17:10 --> $DIR/manual_filter_map.rs:18:10
| |
LL | .filter(|&x| to_ref(to_opt(x)).is_some()) LL | .filter(|&x| to_ref(to_opt(x)).is_some())
| __________^ | __________^
@ -45,13 +45,13 @@ LL | | .map(|y| to_ref(to_opt(y)).unwrap());
| |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:17:22 --> $DIR/manual_filter_map.rs:18:22
| |
LL | .filter(|&x| to_ref(to_opt(x)).is_some()) LL | .filter(|&x| to_ref(to_opt(x)).is_some())
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:20:10 --> $DIR/manual_filter_map.rs:21:10
| |
LL | .filter(|x| to_ref(to_opt(*x)).is_some()) LL | .filter(|x| to_ref(to_opt(*x)).is_some())
| __________^ | __________^
@ -59,13 +59,13 @@ LL | | .map(|y| to_ref(to_opt(y)).unwrap());
| |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:20:21 --> $DIR/manual_filter_map.rs:21:21
| |
LL | .filter(|x| to_ref(to_opt(*x)).is_some()) LL | .filter(|x| to_ref(to_opt(*x)).is_some())
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:24:10 --> $DIR/manual_filter_map.rs:25:10
| |
LL | .filter(|&x| to_ref(to_res(x)).is_ok()) LL | .filter(|&x| to_ref(to_res(x)).is_ok())
| __________^ | __________^
@ -73,13 +73,13 @@ LL | | .map(|y| to_ref(to_res(y)).unwrap());
| |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:24:22 --> $DIR/manual_filter_map.rs:25:22
| |
LL | .filter(|&x| to_ref(to_res(x)).is_ok()) LL | .filter(|&x| to_ref(to_res(x)).is_ok())
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:27:10 --> $DIR/manual_filter_map.rs:28:10
| |
LL | .filter(|x| to_ref(to_res(*x)).is_ok()) LL | .filter(|x| to_ref(to_res(*x)).is_ok())
| __________^ | __________^
@ -87,13 +87,13 @@ LL | | .map(|y| to_ref(to_res(y)).unwrap());
| |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:27:21 --> $DIR/manual_filter_map.rs:28:21
| |
LL | .filter(|x| to_ref(to_res(*x)).is_ok()) LL | .filter(|x| to_ref(to_res(*x)).is_ok())
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:33:27 --> $DIR/manual_filter_map.rs:34:27
| |
LL | iter::<Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); LL | iter::<Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())`
@ -102,79 +102,79 @@ LL | iter::<Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()
= help: to override `-D warnings` add `#[allow(clippy::manual_find_map)]` = help: to override `-D warnings` add `#[allow(clippy::manual_find_map)]`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:34:28 --> $DIR/manual_filter_map.rs:35:28
| |
LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:35:31 --> $DIR/manual_filter_map.rs:36:31
| |
LL | iter::<&Option<String>>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); LL | iter::<&Option<String>>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:36:31 --> $DIR/manual_filter_map.rs:37:31
| |
LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:36:41 --> $DIR/manual_filter_map.rs:37:41
| |
LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^ | ^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:38:30 --> $DIR/manual_filter_map.rs:39:30
| |
LL | iter::<Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:39:31 --> $DIR/manual_filter_map.rs:40:31
| |
LL | iter::<&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:40:32 --> $DIR/manual_filter_map.rs:41:32
| |
LL | iter::<&&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<&&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:41:31 --> $DIR/manual_filter_map.rs:42:31
| |
LL | iter::<Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); LL | iter::<Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:42:32 --> $DIR/manual_filter_map.rs:43:32
| |
LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:43:35 --> $DIR/manual_filter_map.rs:44:35
| |
LL | iter::<&Result<String, ()>>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); LL | iter::<&Result<String, ()>>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_filter_map.rs:44:35 --> $DIR/manual_filter_map.rs:45:35
| |
LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_filter_map.rs:44:45 --> $DIR/manual_filter_map.rs:45:45
| |
LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^ | ^^^^^^^^^
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:92:10 --> $DIR/manual_filter_map.rs:93:10
| |
LL | .filter(|f| f.option_field.is_some()) LL | .filter(|f| f.option_field.is_some())
| __________^ | __________^
@ -182,7 +182,7 @@ LL | | .map(|f| f.option_field.clone().unwrap());
| |_________________________________________________^ help: try: `filter_map(|f| f.option_field.clone())` | |_________________________________________________^ help: try: `filter_map(|f| f.option_field.clone())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:97:10 --> $DIR/manual_filter_map.rs:98:10
| |
LL | .filter(|f| f.ref_field.is_some()) LL | .filter(|f| f.ref_field.is_some())
| __________^ | __________^
@ -190,7 +190,7 @@ LL | | .map(|f| f.ref_field.cloned().unwrap());
| |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.cloned())` | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.cloned())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:102:10 --> $DIR/manual_filter_map.rs:103:10
| |
LL | .filter(|f| f.ref_field.is_some()) LL | .filter(|f| f.ref_field.is_some())
| __________^ | __________^
@ -198,7 +198,7 @@ LL | | .map(|f| f.ref_field.copied().unwrap());
| |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.copied())` | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.copied())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:107:10 --> $DIR/manual_filter_map.rs:108:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -206,7 +206,7 @@ LL | | .map(|f| f.result_field.clone().unwrap());
| |_________________________________________________^ help: try: `filter_map(|f| f.result_field.clone().ok())` | |_________________________________________________^ help: try: `filter_map(|f| f.result_field.clone().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:112:10 --> $DIR/manual_filter_map.rs:113:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -214,7 +214,7 @@ LL | | .map(|f| f.result_field.as_ref().unwrap());
| |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_ref().ok())` | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_ref().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:117:10 --> $DIR/manual_filter_map.rs:118:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -222,7 +222,7 @@ LL | | .map(|f| f.result_field.as_deref().unwrap());
| |____________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref().ok())` | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:122:10 --> $DIR/manual_filter_map.rs:123:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -230,7 +230,7 @@ LL | | .map(|f| f.result_field.as_mut().unwrap());
| |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_mut().ok())` | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_mut().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:127:10 --> $DIR/manual_filter_map.rs:128:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -238,7 +238,7 @@ LL | | .map(|f| f.result_field.as_deref_mut().unwrap());
| |________________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref_mut().ok())` | |________________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref_mut().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:132:10 --> $DIR/manual_filter_map.rs:133:10
| |
LL | .filter(|f| f.result_field.is_ok()) LL | .filter(|f| f.result_field.is_ok())
| __________^ | __________^
@ -246,7 +246,7 @@ LL | | .map(|f| f.result_field.to_owned().unwrap());
| |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:145:27 --> $DIR/manual_filter_map.rs:146:27
| |
LL | let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x { LL | let _x = iter.clone().filter(|x| matches!(x, Enum::A(_))).map(|x| match x {
| ___________________________^ | ___________________________^
@ -256,7 +256,7 @@ LL | | });
| |______^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })` | |______^ help: try: `filter_map(|x| match x { Enum::A(s) => Some(s), _ => None })`
error: `filter(..).map(..)` can be simplified as `filter_map(..)` error: `filter(..).map(..)` can be simplified as `filter_map(..)`
--> $DIR/manual_filter_map.rs:155:10 --> $DIR/manual_filter_map.rs:156:10
| |
LL | .filter(|x| matches!(x, Enum::A(_))) LL | .filter(|x| matches!(x, Enum::A(_)))
| __________^ | __________^

View file

@ -2,6 +2,7 @@
#![warn(clippy::manual_find_map)] #![warn(clippy::manual_find_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure #![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
#![allow(clippy::useless_vec)] #![allow(clippy::useless_vec)]
#![allow(clippy::struct_field_names)]
fn main() { fn main() {
// is_some(), unwrap() // is_some(), unwrap()

View file

@ -2,6 +2,7 @@
#![warn(clippy::manual_find_map)] #![warn(clippy::manual_find_map)]
#![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure #![allow(clippy::redundant_closure)] // FIXME suggestion may have redundant closure
#![allow(clippy::useless_vec)] #![allow(clippy::useless_vec)]
#![allow(clippy::struct_field_names)]
fn main() { fn main() {
// is_some(), unwrap() // is_some(), unwrap()

View file

@ -1,11 +1,11 @@
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:8:19 --> $DIR/manual_find_map.rs:9:19
| |
LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:8:28 --> $DIR/manual_find_map.rs:9:28
| |
LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()); LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap());
| ^^^^^^^^^^ | ^^^^^^^^^^
@ -13,31 +13,31 @@ LL | let _ = (0..).find(|n| to_opt(*n).is_some()).map(|a| to_opt(a).unwrap()
= help: to override `-D warnings` add `#[allow(clippy::manual_find_map)]` = help: to override `-D warnings` add `#[allow(clippy::manual_find_map)]`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:11:19 --> $DIR/manual_find_map.rs:12:19
| |
LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_opt(a))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:11:29 --> $DIR/manual_find_map.rs:12:29
| |
LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi")); LL | let _ = (0..).find(|&n| to_opt(n).is_some()).map(|a| to_opt(a).expect("hi"));
| ^^^^^^^^^ | ^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:14:19 --> $DIR/manual_find_map.rs:15:19
| |
LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:14:29 --> $DIR/manual_find_map.rs:15:29
| |
LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1));
| ^^^^^^^^^ | ^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:17:10 --> $DIR/manual_find_map.rs:18:10
| |
LL | .find(|&x| to_ref(to_opt(x)).is_some()) LL | .find(|&x| to_ref(to_opt(x)).is_some())
| __________^ | __________^
@ -45,13 +45,13 @@ LL | | .map(|y| to_ref(to_opt(y)).unwrap());
| |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:17:20 --> $DIR/manual_find_map.rs:18:20
| |
LL | .find(|&x| to_ref(to_opt(x)).is_some()) LL | .find(|&x| to_ref(to_opt(x)).is_some())
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:20:10 --> $DIR/manual_find_map.rs:21:10
| |
LL | .find(|x| to_ref(to_opt(*x)).is_some()) LL | .find(|x| to_ref(to_opt(*x)).is_some())
| __________^ | __________^
@ -59,13 +59,13 @@ LL | | .map(|y| to_ref(to_opt(y)).unwrap());
| |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:20:19 --> $DIR/manual_find_map.rs:21:19
| |
LL | .find(|x| to_ref(to_opt(*x)).is_some()) LL | .find(|x| to_ref(to_opt(*x)).is_some())
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:24:10 --> $DIR/manual_find_map.rs:25:10
| |
LL | .find(|&x| to_ref(to_res(x)).is_ok()) LL | .find(|&x| to_ref(to_res(x)).is_ok())
| __________^ | __________^
@ -73,13 +73,13 @@ LL | | .map(|y| to_ref(to_res(y)).unwrap());
| |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:24:20 --> $DIR/manual_find_map.rs:25:20
| |
LL | .find(|&x| to_ref(to_res(x)).is_ok()) LL | .find(|&x| to_ref(to_res(x)).is_ok())
| ^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:27:10 --> $DIR/manual_find_map.rs:28:10
| |
LL | .find(|x| to_ref(to_res(*x)).is_ok()) LL | .find(|x| to_ref(to_res(*x)).is_ok())
| __________^ | __________^
@ -87,109 +87,109 @@ LL | | .map(|y| to_ref(to_res(y)).unwrap());
| |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:27:19 --> $DIR/manual_find_map.rs:28:19
| |
LL | .find(|x| to_ref(to_res(*x)).is_ok()) LL | .find(|x| to_ref(to_res(*x)).is_ok())
| ^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:33:26 --> $DIR/manual_find_map.rs:34:26
| |
LL | iter::<Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap()); LL | iter::<Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x)`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:34:27 --> $DIR/manual_find_map.rs:35:27
| |
LL | iter::<&Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap()); LL | iter::<&Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| *x)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| *x)`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:35:28 --> $DIR/manual_find_map.rs:36:28
| |
LL | iter::<&&Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap()); LL | iter::<&&Option<u8>>().find(|x| x.is_some()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| **x)` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| **x)`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:36:27 --> $DIR/manual_find_map.rs:37:27
| |
LL | iter::<Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); LL | iter::<Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:37:28 --> $DIR/manual_find_map.rs:38:28
| |
LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:38:31 --> $DIR/manual_find_map.rs:39:31
| |
LL | iter::<&Option<String>>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); LL | iter::<&Option<String>>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:39:31 --> $DIR/manual_find_map.rs:40:31
| |
LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:39:41 --> $DIR/manual_find_map.rs:40:41
| |
LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Option<&String>>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^ | ^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:41:30 --> $DIR/manual_find_map.rs:42:30
| |
LL | iter::<Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:42:31 --> $DIR/manual_find_map.rs:43:31
| |
LL | iter::<&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:43:32 --> $DIR/manual_find_map.rs:44:32
| |
LL | iter::<&&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap()); LL | iter::<&&Result<u8, ()>>().find(|x| x.is_ok()).map(|x| x.unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:44:31 --> $DIR/manual_find_map.rs:45:31
| |
LL | iter::<Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); LL | iter::<Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:45:32 --> $DIR/manual_find_map.rs:46:32
| |
LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:46:35 --> $DIR/manual_find_map.rs:47:35
| |
LL | iter::<&Result<String, ()>>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); LL | iter::<&Result<String, ()>>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:47:35 --> $DIR/manual_find_map.rs:48:35
| |
LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())`
| |
note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once note: the suggestion might change the behavior of the program when merging `filter` and `map`, because this expression potentially contains side effects and will only execute once
--> $DIR/manual_find_map.rs:47:45 --> $DIR/manual_find_map.rs:48:45
| |
LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); LL | iter::<Result<&String, ()>>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap());
| ^^^^^^^^^ | ^^^^^^^^^
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:95:10 --> $DIR/manual_find_map.rs:96:10
| |
LL | .find(|f| f.option_field.is_some()) LL | .find(|f| f.option_field.is_some())
| __________^ | __________^
@ -197,7 +197,7 @@ LL | | .map(|f| f.option_field.clone().unwrap());
| |_________________________________________________^ help: try: `find_map(|f| f.option_field.clone())` | |_________________________________________________^ help: try: `find_map(|f| f.option_field.clone())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:100:10 --> $DIR/manual_find_map.rs:101:10
| |
LL | .find(|f| f.ref_field.is_some()) LL | .find(|f| f.ref_field.is_some())
| __________^ | __________^
@ -205,7 +205,7 @@ LL | | .map(|f| f.ref_field.cloned().unwrap());
| |_______________________________________________^ help: try: `find_map(|f| f.ref_field.cloned())` | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.cloned())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:105:10 --> $DIR/manual_find_map.rs:106:10
| |
LL | .find(|f| f.ref_field.is_some()) LL | .find(|f| f.ref_field.is_some())
| __________^ | __________^
@ -213,7 +213,7 @@ LL | | .map(|f| f.ref_field.copied().unwrap());
| |_______________________________________________^ help: try: `find_map(|f| f.ref_field.copied())` | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.copied())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:110:10 --> $DIR/manual_find_map.rs:111:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^
@ -221,7 +221,7 @@ LL | | .map(|f| f.result_field.clone().unwrap());
| |_________________________________________________^ help: try: `find_map(|f| f.result_field.clone().ok())` | |_________________________________________________^ help: try: `find_map(|f| f.result_field.clone().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:115:10 --> $DIR/manual_find_map.rs:116:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^
@ -229,7 +229,7 @@ LL | | .map(|f| f.result_field.as_ref().unwrap());
| |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_ref().ok())` | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_ref().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:120:10 --> $DIR/manual_find_map.rs:121:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^
@ -237,7 +237,7 @@ LL | | .map(|f| f.result_field.as_deref().unwrap());
| |____________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref().ok())` | |____________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:125:10 --> $DIR/manual_find_map.rs:126:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^
@ -245,7 +245,7 @@ LL | | .map(|f| f.result_field.as_mut().unwrap());
| |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_mut().ok())` | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_mut().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:130:10 --> $DIR/manual_find_map.rs:131:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^
@ -253,7 +253,7 @@ LL | | .map(|f| f.result_field.as_deref_mut().unwrap());
| |________________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref_mut().ok())` | |________________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref_mut().ok())`
error: `find(..).map(..)` can be simplified as `find_map(..)` error: `find(..).map(..)` can be simplified as `find_map(..)`
--> $DIR/manual_find_map.rs:135:10 --> $DIR/manual_find_map.rs:136:10
| |
LL | .find(|f| f.result_field.is_ok()) LL | .find(|f| f.result_field.is_ok())
| __________^ | __________^

View file

@ -33,6 +33,7 @@ fn msrv_1_23() {
assert!(matches!(b'1', b'0'..=b'9')); assert!(matches!(b'1', b'0'..=b'9'));
assert!(matches!('X', 'A'..='Z')); assert!(matches!('X', 'A'..='Z'));
assert!(matches!('x', 'A'..='Z' | 'a'..='z')); assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
} }
#[clippy::msrv = "1.24"] #[clippy::msrv = "1.24"]
@ -40,14 +41,17 @@ fn msrv_1_24() {
assert!(b'1'.is_ascii_digit()); assert!(b'1'.is_ascii_digit());
assert!('X'.is_ascii_uppercase()); assert!('X'.is_ascii_uppercase());
assert!('x'.is_ascii_alphabetic()); assert!('x'.is_ascii_alphabetic());
assert!('x'.is_ascii_hexdigit());
} }
#[clippy::msrv = "1.46"] #[clippy::msrv = "1.46"]
fn msrv_1_46() { fn msrv_1_46() {
const FOO: bool = matches!('x', '0'..='9'); const FOO: bool = matches!('x', '0'..='9');
const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
} }
#[clippy::msrv = "1.47"] #[clippy::msrv = "1.47"]
fn msrv_1_47() { fn msrv_1_47() {
const FOO: bool = 'x'.is_ascii_digit(); const FOO: bool = 'x'.is_ascii_digit();
const BAR: bool = 'x'.is_ascii_hexdigit();
} }

View file

@ -33,6 +33,7 @@ fn msrv_1_23() {
assert!(matches!(b'1', b'0'..=b'9')); assert!(matches!(b'1', b'0'..=b'9'));
assert!(matches!('X', 'A'..='Z')); assert!(matches!('X', 'A'..='Z'));
assert!(matches!('x', 'A'..='Z' | 'a'..='z')); assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
} }
#[clippy::msrv = "1.24"] #[clippy::msrv = "1.24"]
@ -40,14 +41,17 @@ fn msrv_1_24() {
assert!(matches!(b'1', b'0'..=b'9')); assert!(matches!(b'1', b'0'..=b'9'));
assert!(matches!('X', 'A'..='Z')); assert!(matches!('X', 'A'..='Z'));
assert!(matches!('x', 'A'..='Z' | 'a'..='z')); assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
assert!(matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F'));
} }
#[clippy::msrv = "1.46"] #[clippy::msrv = "1.46"]
fn msrv_1_46() { fn msrv_1_46() {
const FOO: bool = matches!('x', '0'..='9'); const FOO: bool = matches!('x', '0'..='9');
const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
} }
#[clippy::msrv = "1.47"] #[clippy::msrv = "1.47"]
fn msrv_1_47() { fn msrv_1_47() {
const FOO: bool = matches!('x', '0'..='9'); const FOO: bool = matches!('x', '0'..='9');
const BAR: bool = matches!('x', '0'..='9' | 'a'..='f' | 'A'..='F');
} }

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