diff options
| author | Philipp Krones <hello@philkrones.com> | 2024-11-07 22:31:20 +0100 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2024-11-07 22:37:01 +0100 |
| commit | 6ced8c33c058fa1df65a363abcdc5e2c5828fa66 (patch) | |
| tree | 86a24d4cb4011e4f95093ed710c7bb35cdd95304 /clippy_lints/src | |
| parent | 4847c40c8b40cc2a1155204b934f7a3c29178782 (diff) | |
Merge commit 'f712eb5cdccd121d0569af12f20e6a0fabe4364d' into clippy-subtree-update
Diffstat (limited to 'clippy_lints/src')
94 files changed, 1511 insertions, 344 deletions
diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs new file mode 100644 index 00000000000..8719f61a890 --- /dev/null +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -0,0 +1,531 @@ +use clippy_config::Conf; +use clippy_config::types::{ + SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, + SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, +}; +use clippy_utils::diagnostics::span_lint_and_note; +use rustc_hir::{ + AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, UseKind, + Variant, VariantData, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Confirms that items are sorted in source files as per configuration. + /// + /// ### Why restrict this? + /// + /// Keeping a consistent ordering throughout the codebase helps with working + /// as a team, and possibly improves maintainability of the codebase. The + /// idea is that by defining a consistent and enforceable rule for how + /// source files are structured, less time will be wasted during reviews on + /// a topic that is (under most circumstances) not relevant to the logic + /// implemented in the code. Sometimes this will be referred to as + /// "bikeshedding". + /// + /// ### Default Ordering and Configuration + /// + /// As there is no generally applicable rule, and each project may have + /// different requirements, the lint can be configured with high + /// granularity. The configuration is split into two stages: + /// + /// 1. Which item kinds that should have an internal order enforced. + /// 2. Individual ordering rules per item kind. + /// + /// The item kinds that can be linted are: + /// - Module (with customized groupings, alphabetical within) + /// - Trait (with customized order of associated items, alphabetical within) + /// - Enum, Impl, Struct (purely alphabetical) + /// + /// #### Module Item Order + /// + /// Due to the large variation of items within modules, the ordering can be + /// configured on a very granular level. Item kinds can be grouped together + /// arbitrarily, items within groups will be ordered alphabetically. The + /// following table shows the default groupings: + /// + /// | Group | Item Kinds | + /// |--------------------|----------------------| + /// | `modules` | "mod", "foreign_mod" | + /// | `use` | "use" | + /// | `macros` | "macro" | + /// | `global_asm` | "global_asm" | + /// | `UPPER_SNAKE_CASE` | "static", "const" | + /// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" | + /// | `lower_snake_case` | "fn" | + /// + /// All item kinds must be accounted for to create an enforceable linting + /// rule set. + /// + /// ### Known Problems + /// + /// #### Performance Impact + /// + /// Keep in mind, that ordering source code alphabetically can lead to + /// reduced performance in cases where the most commonly used enum variant + /// isn't the first entry anymore, and similar optimizations that can reduce + /// branch misses, cache locality and such. Either don't use this lint if + /// that's relevant, or disable the lint in modules or items specifically + /// where it matters. Other solutions can be to use profile guided + /// optimization (PGO), post-link optimization (e.g. using BOLT for LLVM), + /// or other advanced optimization methods. A good starting point to dig + /// into optimization is [cargo-pgo][cargo-pgo]. + /// + /// #### Lints on a Contains basis + /// + /// The lint can be disabled only on a "contains" basis, but not per element + /// within a "container", e.g. the lint works per-module, per-struct, + /// per-enum, etc. but not for "don't order this particular enum variant". + /// + /// #### Module documentation + /// + /// Module level rustdoc comments are not part of the resulting syntax tree + /// and as such cannot be linted from within `check_mod`. Instead, the + /// `rustdoc::missing_documentation` lint may be used. + /// + /// #### Module Tests + /// + /// This lint does not implement detection of module tests (or other feature + /// dependent elements for that matter). To lint the location of mod tests, + /// the lint `items_after_test_module` can be used instead. + /// + /// ### Example + /// + /// ```no_run + /// trait TraitUnordered { + /// const A: bool; + /// const C: bool; + /// const B: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn c(); + /// fn b(); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// trait TraitOrdered { + /// const A: bool; + /// const B: bool; + /// const C: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn b(); + /// fn c(); + /// } + /// ``` + /// + /// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md + /// + #[clippy::version = "1.82.0"] + pub ARBITRARY_SOURCE_ITEM_ORDERING, + restriction, + "arbitrary source item ordering" +} + +impl_lint_pass!(ArbitrarySourceItemOrdering => [ARBITRARY_SOURCE_ITEM_ORDERING]); + +#[derive(Debug)] +#[allow(clippy::struct_excessive_bools)] // Bools are cached feature flags. +pub struct ArbitrarySourceItemOrdering { + assoc_types_order: SourceItemOrderingTraitAssocItemKinds, + enable_ordering_for_enum: bool, + enable_ordering_for_impl: bool, + enable_ordering_for_module: bool, + enable_ordering_for_struct: bool, + enable_ordering_for_trait: bool, + module_item_order_groupings: SourceItemOrderingModuleItemGroupings, +} + +impl ArbitrarySourceItemOrdering { + pub fn new(conf: &'static Conf) -> Self { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingCategory::*; + Self { + assoc_types_order: conf.trait_assoc_item_kinds_order.clone(), + enable_ordering_for_enum: conf.source_item_ordering.contains(&Enum), + enable_ordering_for_impl: conf.source_item_ordering.contains(&Impl), + enable_ordering_for_module: conf.source_item_ordering.contains(&Module), + enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct), + enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait), + module_item_order_groupings: conf.module_item_order_groupings.clone(), + } + } + + /// Produces a linting warning for incorrectly ordered impl items. + fn lint_impl_item<T: LintContext>(&self, cx: &T, item: &ImplItemRef, before_item: &ImplItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of impl items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } + + /// Produces a linting warning for incorrectly ordered item members. + fn lint_member_name<T: LintContext>( + cx: &T, + ident: &rustc_span::symbol::Ident, + before_ident: &rustc_span::symbol::Ident, + ) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + ident.span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(before_ident.span), + format!("should be placed before `{}`", before_ident.as_str(),), + ); + } + + fn lint_member_item<T: LintContext>(cx: &T, item: &Item<'_>, before_item: &Item<'_>) { + let span = if item.ident.as_str().is_empty() { + &item.span + } else { + &item.ident.span + }; + + let (before_span, note) = if before_item.ident.as_str().is_empty() { + ( + &before_item.span, + "should be placed before the following item".to_owned(), + ) + } else { + ( + &before_item.ident.span, + format!("should be placed before `{}`", before_item.ident.as_str(),), + ) + }; + + // This catches false positives where generated code gets linted. + if span == before_span { + return; + } + + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + *span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(*before_span), + note, + ); + } + + /// Produces a linting warning for incorrectly ordered trait items. + fn lint_trait_item<T: LintContext>(&self, cx: &T, item: &TraitItemRef, before_item: &TraitItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of trait items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + match &item.kind { + ItemKind::Enum(enum_def, _generics) if self.enable_ordering_for_enum => { + let mut cur_v: Option<&Variant<'_>> = None; + for variant in enum_def.variants { + if in_external_macro(cx.sess(), variant.span) { + continue; + } + + if let Some(cur_v) = cur_v { + if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span { + Self::lint_member_name(cx, &variant.ident, &cur_v.ident); + } + } + cur_v = Some(variant); + } + }, + ItemKind::Struct(VariantData::Struct { fields, .. }, _generics) if self.enable_ordering_for_struct => { + let mut cur_f: Option<&FieldDef<'_>> = None; + for field in *fields { + if in_external_macro(cx.sess(), field.span) { + continue; + } + + if let Some(cur_f) = cur_f { + if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span { + Self::lint_member_name(cx, &field.ident, &cur_f.ident); + } + } + cur_f = Some(field); + } + }, + ItemKind::Trait(is_auto, _safety, _generics, _generic_bounds, item_ref) + if self.enable_ordering_for_trait && *is_auto == IsAuto::No => + { + let mut cur_t: Option<&TraitItemRef> = None; + + for item in *item_ref { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_trait_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + ItemKind::Impl(trait_impl) if self.enable_ordering_for_impl => { + let mut cur_t: Option<&ImplItemRef> = None; + + for item in trait_impl.items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_impl_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + _ => {}, // Catch-all for `ItemKinds` that don't have fields. + } + } + + fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) { + struct CurItem<'a> { + item: &'a Item<'a>, + order: usize, + name: String, + } + let mut cur_t: Option<CurItem<'_>> = None; + + if !self.enable_ordering_for_module { + return; + } + + let items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id)); + + // Iterates over the items within a module. + // + // As of 2023-05-09, the Rust compiler will hold the entries in the same + // order as they appear in the source code, which is convenient for us, + // as no sorting by source map/line of code has to be applied. + // + for item in items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + // The following exceptions (skipping with `continue;`) may not be + // complete, edge cases have not been explored further than what + // appears in the existing code base. + if item.ident.name == rustc_span::symbol::kw::Empty { + if let ItemKind::Impl(_) = item.kind { + // Sorting trait impls for unnamed types makes no sense. + if get_item_name(item).is_empty() { + continue; + } + } else if let ItemKind::ForeignMod { .. } = item.kind { + continue; + } else if let ItemKind::GlobalAsm(_) = item.kind { + continue; + } else if let ItemKind::Use(path, use_kind) = item.kind { + if path.segments.is_empty() { + // Use statements that contain braces get caught here. + // They will still be linted internally. + continue; + } else if path.segments.len() >= 2 + && (path.segments[0].ident.name == rustc_span::sym::std + || path.segments[0].ident.name == rustc_span::sym::core) + && path.segments[1].ident.name == rustc_span::sym::prelude + { + // Filters the autogenerated prelude use statement. + // e.g. `use std::prelude::rustc_2021` + } else if use_kind == UseKind::Glob { + // Filters glob kinds of uses. + // e.g. `use std::sync::*` + } else { + // This can be used for debugging. + // println!("Unknown autogenerated use statement: {:?}", item); + } + continue; + } + } + + if item.ident.name.as_str().starts_with('_') { + // Filters out unnamed macro-like impls for various derives, + // e.g. serde::Serialize or num_derive::FromPrimitive. + continue; + } + + if item.ident.name == rustc_span::sym::std && item.span.is_dummy() { + if let ItemKind::ExternCrate(None) = item.kind { + // Filters the auto-included Rust standard library. + continue; + } + println!("Unknown item: {item:?}"); + } + + let item_kind = convert_module_item_kind(&item.kind); + let module_level_order = self + .module_item_order_groupings + .module_level_order_of(&item_kind) + .unwrap_or_default(); + + if let Some(cur_t) = cur_t.as_ref() { + use std::cmp::Ordering; // Better legibility. + match module_level_order.cmp(&cur_t.order) { + Ordering::Less => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => { + // Skip ordering use statements, as these should be ordered by rustfmt. + }, + Ordering::Equal if cur_t.name > get_item_name(item) => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal | Ordering::Greater => { + // Nothing to do in this case, they're already in the right order. + }, + } + } + + // Makes a note of the current item for comparison with the next. + cur_t = Some(CurItem { + order: module_level_order, + item, + name: get_item_name(item), + }); + } + } +} + +/// Converts a [`rustc_hir::AssocItemKind`] to a +/// [`SourceItemOrderingTraitAssocItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_assoc_item_kind(value: AssocItemKind) -> SourceItemOrderingTraitAssocItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + match value { + AssocItemKind::Const { .. } => Const, + AssocItemKind::Type { .. } => Type, + AssocItemKind::Fn { .. } => Fn, + } +} + +/// Converts a [`rustc_hir::ItemKind`] to a +/// [`SourceItemOrderingModuleItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + match value { + ItemKind::ExternCrate(..) => ExternCrate, + ItemKind::Use(..) => Use, + ItemKind::Static(..) => Static, + ItemKind::Const(..) => Const, + ItemKind::Fn(..) => Fn, + ItemKind::Macro(..) => Macro, + ItemKind::Mod(..) => Mod, + ItemKind::ForeignMod { .. } => ForeignMod, + ItemKind::GlobalAsm(..) => GlobalAsm, + ItemKind::TyAlias(..) => TyAlias, + ItemKind::Enum(..) => Enum, + ItemKind::Struct(..) => Struct, + ItemKind::Union(..) => Union, + ItemKind::Trait(..) => Trait, + ItemKind::TraitAlias(..) => TraitAlias, + ItemKind::Impl(..) => Impl, + } +} + +/// Gets the item name for sorting purposes, which in the general case is +/// `item.ident.name`. +/// +/// For trait impls, the name used for sorting will be the written path of +/// `item.self_ty` plus the written path of `item.of_trait`, joined with +/// exclamation marks. Exclamation marks are used because they are the first +/// printable ASCII character. +/// +/// Trait impls generated using a derive-macro will have their path rewritten, +/// such that for example `Default` is `$crate::default::Default`, and +/// `std::clone::Clone` is `$crate::clone::Clone`. This behaviour is described +/// further in the [Rust Reference, Paths Chapter][rust_ref]. +/// +/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1 +fn get_item_name(item: &Item<'_>) -> String { + match item.kind { + ItemKind::Impl(im) => { + if let TyKind::Path(path) = im.self_ty.kind { + match path { + QPath::Resolved(_, path) => { + let segs = path.segments.iter(); + let mut segs: Vec<String> = segs.map(|s| s.ident.name.as_str().to_owned()).collect(); + + if let Some(of_trait) = im.of_trait { + let mut trait_segs: Vec<String> = of_trait + .path + .segments + .iter() + .map(|s| s.ident.name.as_str().to_owned()) + .collect(); + segs.append(&mut trait_segs); + } + + segs.push(String::new()); + segs.join("!!") + }, + QPath::TypeRelative(_, _path_seg) => { + // This case doesn't exist in the clippy tests codebase. + String::new() + }, + QPath::LangItem(_, _) => String::new(), + } + } else { + // Impls for anything that isn't a named type can be skipped. + String::new() + } + }, + _ => item.ident.name.as_str().to_owned(), + } +} diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 684756ce87f..c29c9f08cf7 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -14,7 +14,7 @@ mod utils; use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; -use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast}; +use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 92b9f9cba52..dc5a6857089 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -1,10 +1,9 @@ -use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use super::USELESS_ATTRIBUTE; +use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, first_line_of_span}; -use rustc_ast::MetaItemInner; +use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner}; use rustc_errors::Applicability; -use rustc_ast::{Item, ItemKind, Attribute}; use rustc_lint::{EarlyContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_span::sym; diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 9952d0af99e..6948cf560a5 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// `MutexGuard`. /// /// ### Why is this bad? - /// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and + /// The Mutex types found in [`std::sync`](https://doc.rust-lang.org/stable/std/sync/) and /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are /// not designed to operate in an async context across await points. /// diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 26888f7e3a0..26a20bc99a0 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) { && let Some(suggestion) = simplify_not(cx, msrv, inner) && cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow { + use clippy_utils::sugg::{Sugg, has_enclosing_paren}; + let maybe_par = if let Some(sug) = Sugg::hir_opt(cx, inner) { + match sug { + Sugg::BinOp(..) => true, + Sugg::MaybeParen(sug) if !has_enclosing_paren(&sug) => true, + _ => false, + } + } else { + false + }; + let suggestion = if maybe_par { + format!("({suggestion})") + } else { + suggestion + }; span_lint_and_sugg( cx, NONMINIMAL_BOOL, diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index cba8224b84c..f2551a05b1a 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -4,7 +4,7 @@ use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed}; use rustc_errors::Applicability; -use rustc_hir::{ExprKind, UnOp}; +use rustc_hir::{BorrowKind, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::Mutability; use rustc_middle::ty; @@ -49,7 +49,7 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]); impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { - if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..)) && !e.span.from_expansion() diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index b11b967f8bc..205357afd84 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { ); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind { - if method_path.ident.name == sym!(cast) + if method_path.ident.name.as_str() == "cast" && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args // There probably is no obvious reason to do this, just to be consistent with `as` cases. diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 811d33c8bde..abd80abffe6 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -268,8 +268,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx if !snippet .split("->") .skip(1) - .map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) - .any(|a| a) + .any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) { return ControlFlow::Break(()); } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 2e7f91a842e..f76e399517c 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -232,7 +232,7 @@ fn get_types_from_cast<'a>( // or `to_type::MAX as from_type` let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind // to_type::max_value(), from_type - && let TyKind::Path(ref from_type_path) = &from_type.kind + && let TyKind::Path(from_type_path) = &from_type.kind && let Some(from_sym) = int_ty_to_sym(from_type_path) { Some((limit, from_sym)) @@ -245,7 +245,7 @@ fn get_types_from_cast<'a>( if let ExprKind::Call(from_func, [limit]) = &expr.kind // `from_type::from, to_type::max_value()` // `from_type::from` - && let ExprKind::Path(ref path) = &from_func.kind + && let ExprKind::Path(path) = &from_func.kind && let Some(from_sym) = get_implementing_type(path, INTS, "from") { Some((limit, from_sym)) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 477435236a5..383fae7992b 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { #[clippy::version = "1.35.0"] pub COGNITIVE_COMPLEXITY, nursery, - "functions that should be split up into multiple functions" + "functions that should be split up into multiple functions", @eval_always = true } diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index d90a22bb62a..c4afdc757d8 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo .iter() .filter(|&&(_, name)| !name.as_str().starts_with('_')) .any(|&(_, name)| { - let mut walker = ContainsName { - name, - result: false, - cx, - }; + let mut walker = ContainsName { name, cx }; // Scan block - block + let mut res = block .stmts .iter() .filter(|stmt| !ignore_span.overlaps(stmt.span)) - .for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); + .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); if let Some(expr) = block.expr { - intravisit::walk_expr(&mut walker, expr); + if res.is_continue() { + res = intravisit::walk_expr(&mut walker, expr); + } } - walker.result + res.is_break() }) }) } diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs index 50fd76a3a47..4ecf3e41611 100644 --- a/clippy_lints/src/copy_iterator.rs +++ b/clippy_lints/src/copy_iterator.rs @@ -37,7 +37,7 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]); impl<'tcx> LateLintPass<'tcx> for CopyIterator { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() diff --git a/clippy_lints/src/ctfe.rs b/clippy_lints/src/ctfe.rs index 181514e8372..589b99518a0 100644 --- a/clippy_lints/src/ctfe.rs +++ b/clippy_lints/src/ctfe.rs @@ -5,7 +5,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; - declare_lint_pass! { /// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran. diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs index a785a9d377c..4d908af4084 100644 --- a/clippy_lints/src/declare_clippy_lint.rs +++ b/clippy_lints/src/declare_clippy_lint.rs @@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint { (@ $(#[doc = $lit:literal])* pub $lint_name:ident, - $category:ident, + $level:ident, $lintcategory:expr, $desc:literal, $version_expr:expr, @@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint { $(#[doc = $lit])* #[clippy::version = $version_lit] pub clippy::$lint_name, - $category, + $level, $desc, report_in_external_macro:true $(, @eval_always = $eval_always)? @@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, restriction, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -49,12 +50,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, style, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Style, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -63,12 +65,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, correctness, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; @@ -78,12 +81,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, perf, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Perf, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -92,12 +96,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, complexity, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -106,12 +111,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, suspicious, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -120,12 +126,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, nursery, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -134,12 +141,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, pedantic, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -148,12 +156,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, cargo, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3c4e75df8ab..edb52851e0c 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -28,12 +28,15 @@ pub static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, #[cfg(feature = "internal")] + crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO, + #[cfg(feature = "internal")] crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO, crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, + crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO, crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO, crate::as_conversions::AS_CONVERSIONS_INFO, crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, @@ -414,14 +417,17 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_SPLIT_ONCE_INFO, crate::methods::MANUAL_STR_REPEAT_INFO, crate::methods::MANUAL_TRY_FOLD_INFO, + crate::methods::MAP_ALL_ANY_IDENTITY_INFO, crate::methods::MAP_CLONE_INFO, crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, crate::methods::MAP_ERR_IGNORE_INFO, crate::methods::MAP_FLATTEN_INFO, crate::methods::MAP_IDENTITY_INFO, crate::methods::MAP_UNWRAP_OR_INFO, + crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, crate::methods::MUT_MUTEX_LOCK_INFO, crate::methods::NAIVE_BYTECOUNT_INFO, + crate::methods::NEEDLESS_AS_BYTES_INFO, crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, crate::methods::NEEDLESS_COLLECT_INFO, crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 2920bbb4c81..2b6bfafe695 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -187,7 +187,7 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex impl<'tcx> LateLintPass<'tcx> for DerivableImpls { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], self_ty, .. diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index e8e21edd494..e569c4dc786 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; @@ -202,7 +204,7 @@ declare_lint_pass!(Derive => [ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind { @@ -371,9 +373,8 @@ fn check_unsafe_derive_deserialize<'tcx>( ty: Ty<'tcx>, ) { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { - let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; - walk_item(&mut visitor, item); - visitor.has_unsafe + let mut visitor = UnsafeVisitor { cx }; + walk_item(&mut visitor, item).is_break() } if let Some(trait_def_id) = trait_ref.trait_def_id() @@ -406,38 +407,37 @@ fn check_unsafe_derive_deserialize<'tcx>( struct UnsafeVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - has_unsafe: bool, } impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { - if self.has_unsafe { - return; - } - + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) -> Self::Result { if let Some(header) = kind.header() && header.safety == Safety::Unsafe { - self.has_unsafe = true; + ControlFlow::Break(()) + } else { + walk_fn(self, kind, decl, body_id, id) } - - walk_fn(self, kind, decl, body_id, id); } - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.has_unsafe { - return; - } - + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::Block(block, _) = expr.kind { if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - self.has_unsafe = true; + return ControlFlow::Break(()); } } - walk_expr(self, expr); + walk_expr(self, expr) } fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index b51d343132b..bdd49bf8aa7 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -2,7 +2,6 @@ use clippy_config::Conf; use clippy_utils::create_disallowed_map; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::macros::macro_backtrace; -use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Diag; use rustc_hir::def_id::DefIdMap; @@ -14,6 +13,8 @@ use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{ExpnId, MacroKind, Span}; +use crate::utils::attr_collector::AttrStorage; + declare_clippy_lint! { /// ### What it does /// Denies the configured macros in clippy.toml @@ -64,14 +65,19 @@ pub struct DisallowedMacros { // Track the most recently seen node that can have a `derive` attribute. // Needed to use the correct lint level. derive_src: Option<OwnerId>, + + // When a macro is disallowed in an early pass, it's stored + // and emitted during the late pass. This happens for attributes. + earlies: AttrStorage, } impl DisallowedMacros { - pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { Self { disallowed: create_disallowed_map(tcx, &conf.disallowed_macros), seen: FxHashSet::default(), derive_src: None, + earlies, } } @@ -114,6 +120,15 @@ impl DisallowedMacros { impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl LateLintPass<'_> for DisallowedMacros { + fn check_crate(&mut self, cx: &LateContext<'_>) { + // once we check a crate in the late pass we can emit the early pass lints + if let Some(attr_spans) = self.earlies.clone().0.get() { + for span in attr_spans { + self.check(cx, *span, None); + } + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { self.check(cx, expr.span, None); // `$t + $t` can have the context of $t, check also the span of the binary operator @@ -164,8 +179,4 @@ impl LateLintPass<'_> for DisallowedMacros { fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { self.check(cx, path.span, None); } - - fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { - self.check(cx, attr.span, self.derive_src); - } } diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index f79264e6b04..53c24a3faf1 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -80,7 +80,7 @@ impl EarlyLintPass for DisallowedScriptIdents { let mut symbols: Vec<_> = symbols.iter().collect(); symbols.sort_unstable_by_key(|k| k.1); - for (symbol, &span) in &symbols { + for &(symbol, &span) in &symbols { // Note: `symbol.as_str()` is an expensive operation, thus should not be called // more than once for a single symbol. let symbol_str = symbol.as_str(); diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 89c6a4e08dc..df7c37a192a 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -427,11 +427,11 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks if the first line in the documentation of items listed in module page is too long. + /// Checks if the first paragraph in the documentation of items listed in the module page is too long. /// /// ### Why is this bad? - /// Documentation will show the first paragraph of the doscstring in the summary page of a - /// module, so having a nice, short summary in the first paragraph is part of writing good docs. + /// Documentation will show the first paragraph of the docstring in the summary page of a + /// module. Having a nice, short summary in the first paragraph is part of writing good docs. /// /// ### Example /// ```no_run @@ -453,7 +453,7 @@ declare_clippy_lint! { #[clippy::version = "1.82.0"] pub TOO_LONG_FIRST_DOC_PARAGRAPH, nursery, - "ensure that the first line of a documentation paragraph isn't too long" + "ensure the first documentation paragraph is short" } declare_clippy_lint! { diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index b66dd2108fc..10a84b1b2ff 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -36,7 +36,7 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]); impl LateLintPass<'_> for EmptyDrop { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], .. }) = item.kind diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 6ca599ed361..de10b7bf533 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, - TypeckResults, + self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -204,11 +203,16 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc // 'cuz currently nothing changes after deleting this check. local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) }) { - match cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { + match cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { // Mutable closure is used after current expr; we cannot consume it. Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 4e4434ec7d1..0550c22761a 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { // match call to write_fmt && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind) && let ExprKind::Call(write_recv_path, []) = write_recv.kind - && write_fun.ident.name == sym!(write_fmt) + && write_fun.ident.name.as_str() == "write_fmt" && let Some(def_id) = path_def_id(cx, write_recv_path) { // match calls to std::io::stdout() / std::io::stderr () diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index d5a2e06863d..14da0b515a5 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; @@ -115,25 +117,26 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { } /// Finds the occurrences of `Self` and `self` +/// +/// Returns `ControlFlow::break` if any of the `self`/`Self` usages were from an expansion, or the +/// body contained a binding already named `val`. struct SelfFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// Occurrences of `Self` upper: Vec<Span>, /// Occurrences of `self` lower: Vec<Span>, - /// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding - /// already named `val` - invalid: bool, } impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { + fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result { for segment in path.segments { match segment.ident.name { kw::SelfLower => self.lower.push(segment.ident.span), @@ -141,17 +144,19 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { _ => continue, } - self.invalid |= segment.ident.span.from_expansion(); + if segment.ident.span.from_expansion() { + return ControlFlow::Break(()); + } } - if !self.invalid { - walk_path(self, path); - } + walk_path(self, path) } - fn visit_name(&mut self, name: Symbol) { + fn visit_name(&mut self, name: Symbol) -> Self::Result { if name == sym::val { - self.invalid = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } } @@ -209,11 +214,9 @@ fn convert_to_from( cx, upper: Vec::new(), lower: Vec::new(), - invalid: false, }; - finder.visit_expr(body.value); - if finder.invalid { + if finder.visit_expr(body.value).is_break() { return None; } diff --git a/clippy_lints/src/from_raw_with_void_ptr.rs b/clippy_lints/src/from_raw_with_void_ptr.rs index d62d008d480..c8828c93615 100644 --- a/clippy_lints/src/from_raw_with_void_ptr.rs +++ b/clippy_lints/src/from_raw_with_void_ptr.rs @@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Call(box_from_raw, [arg]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind - && seg.ident.name == sym!(from_raw) + && seg.ident.name.as_str() == "from_raw" && let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id)) && let arg_kind = cx.typeck_results().expr_ty(arg).kind() && let ty::RawPtr(ty, _) = arg_kind diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 91d73c2a9c9..be3d0f7ad63 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -441,7 +441,7 @@ declare_clippy_lint! { /// fn bar(&self) -> Option<&String> { None } /// # } /// ``` - #[clippy::version = "1.82.0"] + #[clippy::version = "1.83.0"] pub REF_OPTION, pedantic, "function signature uses `&Option<T>` instead of `Option<&T>`" diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index 373ecd74cb3..aba0fbcb9fe 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -15,9 +15,9 @@ use rustc_span::{Span, sym}; fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() && is_type_diagnostic_item(cx, *opt_ty, sym::Option) - && let ty::Adt(_, opt_gen) = opt_ty.kind() - && let [gen] = opt_gen.as_slice() - && let GenericArgKind::Type(gen_ty) = gen.unpack() + && let ty::Adt(_, opt_gen_args) = opt_ty.kind() + && let [gen_arg] = opt_gen_args.as_slice() + && let GenericArgKind::Type(gen_ty) = gen_arg.unpack() && !gen_ty.is_ref() // Need to gen the original spans, so first parsing mid, and hir parsing afterward && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 4c5375730b8..c370f206c8f 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -340,7 +340,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashMap::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( @@ -352,7 +352,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashSet::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 71c317552ee..48874d6064b 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { .and(cap); } } - if method.ident.name == sym!(flat_map) && args.len() == 1 { + if method.ident.name.as_str() == "flat_map" && args.len() == 1 { if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind { let body = cx.tcx.hir().body(body); return is_infinite(cx, body.value); @@ -224,7 +224,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { return MaybeInfinite.and(is_infinite(cx, receiver)); } } - if method.ident.name == sym!(last) && args.is_empty() { + if method.ident.name.as_str() == "last" && args.is_empty() { let not_double_ended = cx .tcx .get_diagnostic_item(sym::DoubleEndedIterator) @@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { if not_double_ended { return is_infinite(cx, receiver); } - } else if method.ident.name == sym!(collect) { + } else if method.ident.name.as_str() == "collect" { let ty = cx.typeck_results().expr_ty(expr); if matches!( get_type_diagnostic_name(cx, ty), diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 1e6404190d3..314d0dfa26c 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -142,7 +142,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter { ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() }) && let Some(iter_assoc_span) = imp.items.iter().find_map(|item| { - if item.ident.name == sym!(IntoIter) { + if item.ident.name.as_str() == "IntoIter" { Some(cx.tcx.hir().impl_item(item.id).expect_type().span) } else { None @@ -247,8 +247,8 @@ impl {self_ty_without_ref} {{ let sugg = format!( " impl IntoIterator for {self_ty_snippet} {{ - type IntoIter = {ret_ty}; type Item = {iter_ty}; + type IntoIter = {ret_ty}; fn into_iter(self) -> Self::IntoIter {{ self.iter() }} diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 5d2b521b250..aa8b4d88d2f 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, ConstKind}; +use rustc_middle::ty::{self, ParamEnv}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Pos, Span}; @@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && !item.span.from_expansion() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() - && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() + && let Ok((_, ty::ValTree::Leaf(element_count))) = cst.eval_valtree(cx.tcx, ParamEnv::empty(), item.span) && let element_count = element_count.to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index f2f841dcec3..ab3d19f89c3 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,11 +1,12 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; -use rustc_ast::LitKind; +use clippy_utils::source::snippet_opt; +use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind, Attribute, LitKind}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::sym; +use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -51,6 +52,24 @@ impl LargeIncludeFile { impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); +impl LargeIncludeFile { + fn emit_lint(&self, cx: &LateContext<'_>, span: Span) { + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + cx, + LARGE_INCLUDE_FILE, + span, + "attempted to include a large file", + |diag| { + diag.note(format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + )); + }, + ); + } +} + impl LateLintPass<'_> for LargeIncludeFile { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Lit(lit) = &expr.kind @@ -66,19 +85,33 @@ impl LateLintPass<'_> for LargeIncludeFile { && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - LARGE_INCLUDE_FILE, - expr.span.source_callsite(), - "attempted to include a large file", - |diag| { - diag.note(format!( - "the configuration allows a maximum size of {} bytes", - self.max_file_size - )); - }, - ); + self.emit_lint(cx, expr.span.source_callsite()); + } + } + + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { + if !attr.span.from_expansion() + // Currently, rustc limits the usage of macro at the top-level of attributes, + // so we don't need to recurse into each level. + && let AttrKind::Normal(ref normal) = attr.kind + && let AttrArgs::Eq(_, AttrArgsEq::Hir(ref meta)) = normal.item.args + && !attr.span.contains(meta.span) + // Since the `include_str` is already expanded at this point, we can only take the + // whole attribute snippet and then modify for our suggestion. + && let Some(snippet) = snippet_opt(cx, attr.span) + // We cannot remove this because a `#[doc = include_str!("...")]` attribute can + // occupy several lines. + && let Some(start) = snippet.find('[') + && let Some(end) = snippet.rfind(']') + && let snippet = &snippet[start + 1..end] + // We check that the expansion actually comes from `include_str!` and not just from + // another macro. + && let Some(sub_snippet) = snippet.trim().strip_prefix("doc") + && let Some(sub_snippet) = sub_snippet.trim().strip_prefix("=") + && let sub_snippet = sub_snippet.trim() + && (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!")) + { + self.emit_lint(cx, attr.span); } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 47c65ee6d0b..b7887ef76a5 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -629,7 +629,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { .filter_by_name_unhygienic(is_empty) .any(|item| is_is_empty(cx, item)) }), - ty::Alias(ty::Projection, ref proj) => has_is_empty_impl(cx, proj.def_id), + ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), ty::Adt(id, _) => has_is_empty_impl(cx, id.did()), ty::Array(..) | ty::Slice(..) | ty::Str => true, _ => false, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3fd07ced0e4..c9064df25ac 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -73,6 +73,7 @@ pub mod deprecated_lints; mod absolute_paths; mod almost_complete_range; mod approx_const; +mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; mod asm_syntax; @@ -400,6 +401,7 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; +use utils::attr_collector::{AttrCollector, AttrStorage}; /// Register all pre expansion lints /// @@ -464,6 +466,7 @@ pub(crate) enum LintCategory { #[cfg(feature = "internal")] Internal, } + #[allow(clippy::enum_glob_use)] use LintCategory::*; @@ -585,6 +588,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { )) }); + let attr_storage = AttrStorage::default(); + let attrs = attr_storage.clone(); + store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); + // all the internal lints #[cfg(feature = "internal")] { @@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| { Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new()) }); + store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons)); } store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe)); @@ -795,7 +803,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); - store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf))); + let attrs = attr_storage.clone(); + store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); @@ -953,5 +962,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); + store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 119e410b91c..ee561ea85ed 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -29,11 +29,7 @@ pub(super) fn check( if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) { return; } - } else if count - .try_to_target_usize(cx.tcx) - .map_or(true, |x| x > 32) - && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) - { + } else if count.try_to_target_usize(cx.tcx).map_or(true, |x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) { return; } } diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index e25c03db534..9f543b79bac 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -1,12 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use hir::intravisit::{Visitor, walk_expr}; -use hir::{Expr, ExprKind, FnRetTy, FnSig, Node}; +use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; +use rustc_span::sym; use super::INFINITE_LOOP; @@ -25,13 +26,7 @@ pub(super) fn check<'tcx>( return; }; // Or, its parent function is already returning `Never` - if matches!( - parent_fn_ret, - FnRetTy::Return(hir::Ty { - kind: hir::TyKind::Never, - .. - }) - ) { + if is_never_return(parent_fn_ret) { return; } @@ -69,6 +64,16 @@ pub(super) fn check<'tcx>( fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> { for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) { match parent_node { + // Skip `Coroutine` closures, these are the body of `async fn`, not async closures. + // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty. + Node::Expr(Expr { + kind: + ExprKind::Closure(hir::Closure { + kind: hir::ClosureKind::Coroutine(_), + .. + }), + .. + }) => (), Node::Item(hir::Item { kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _), .. @@ -143,3 +148,41 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> { } } } + +/// Return `true` if the given [`FnRetTy`] is never (!). +/// +/// Note: This function also take care of return type of async fn, +/// as the actual type is behind an [`OpaqueDef`](TyKind::OpaqueDef). +fn is_never_return(ret_ty: FnRetTy<'_>) -> bool { + let FnRetTy::Return(hir_ty) = ret_ty else { return false }; + + match hir_ty.kind { + TyKind::Never => true, + TyKind::OpaqueDef(hir::OpaqueTy { + origin: hir::OpaqueTyOrigin::AsyncFn { .. }, + bounds, + .. + }) => { + if let Some(trait_ref) = bounds.iter().find_map(|b| b.trait_ref()) + && let Some(segment) = trait_ref + .path + .segments + .iter() + .find(|seg| seg.ident.name == sym::future_trait) + && let Some(args) = segment.args + && let Some(cst_kind) = args + .constraints + .iter() + .find_map(|cst| (cst.ident.name == sym::Output).then_some(cst.kind)) + && let hir::AssocItemConstraintKind::Equality { + term: hir::Term::Ty(ty), + } = cst_kind + { + matches!(ty.kind, TyKind::Never) + } else { + false + } + }, + _ => false, + } +} diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index 1a1cde3c5bd..7da4fa76e2c 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -19,10 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &' cx, ids: HirIdSet::default(), def_ids: DefIdMap::default(), - skip: false, }; - var_visitor.visit_expr(cond); - if var_visitor.skip { + if var_visitor.visit_expr(cond).is_break() { return; } let used_in_condition = &var_visitor.ids; @@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, ids: HirIdSet, def_ids: DefIdMap<bool>, - skip: bool, } impl<'tcx> VarCollectorVisitor<'_, 'tcx> { @@ -104,11 +101,15 @@ impl<'tcx> VarCollectorVisitor<'_, 'tcx> { } impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + type Result = ControlFlow<()>; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { match ex.kind { - ExprKind::Path(_) => self.insert_def_id(ex), + ExprKind::Path(_) => { + self.insert_def_id(ex); + ControlFlow::Continue(()) + }, // If there is any function/method call… we just stop analysis - ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + ExprKind::Call(..) | ExprKind::MethodCall(..) => ControlFlow::Break(()), _ => walk_expr(self, ex), } diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 74467522619..b7e37c1a876 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; @@ -204,35 +206,32 @@ fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tc struct V<'a, 'b, 'tcx> { cx: &'a LateContext<'tcx>, iter_expr: &'b IterExpr, - uses_iter: bool, } impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> { - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.uses_iter { - // return - } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { - self.uses_iter = true; + type Result = ControlFlow<()>; + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { + if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { if is_res_used(self.cx, self.iter_expr.path, id) { - self.uses_iter = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } else { - walk_expr(self, e); + walk_expr(self, e) } } } - let mut v = V { - cx, - iter_expr, - uses_iter: false, - }; - v.visit_expr(container); - v.uses_iter + let mut v = V { cx, iter_expr }; + v.visit_expr(container).is_break() } #[expect(clippy::too_many_lines)] @@ -242,34 +241,38 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & iter_expr: &'b IterExpr, loop_id: HirId, after_loop: bool, - used_iter: bool, } impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { type NestedFilter = OnlyBodies; + type Result = ControlFlow<()>; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.used_iter { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { if self.after_loop { if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { - self.used_iter = true; + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { - self.used_iter = is_res_used(self.cx, self.iter_expr.path, id); + if is_res_used(self.cx, self.iter_expr.path, id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } } else { - walk_expr(self, e); + walk_expr(self, e) } } else if self.loop_id == e.hir_id { self.after_loop = true; + ControlFlow::Continue(()) } else { - walk_expr(self, e); + walk_expr(self, e) } } } @@ -347,9 +350,8 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & iter_expr, loop_id: loop_expr.hir_id, after_loop: false, - used_iter: false, }; - v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value); - v.used_iter + v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value) + .is_break() } } diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index c904137da1a..3c77db84a40 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -106,13 +106,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>) -> Option<&'tcx TraitRef<'tcx>> { if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { - if let GenericBound::Trait(poly) = bound { - Some(&poly.trait_ref) - } else { - None - } - }) - && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() + if let GenericBound::Trait(poly) = bound { + Some(&poly.trait_ref) + } else { + None + } + }) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() { return Some(trait_ref); } @@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de .tcx .opaque_captured_lifetimes(opaque_def_id) .iter() - .filter(|&(lifetime, _)| match *lifetime { - ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true, - _ => false, + .filter(|&(lifetime, _)| { + matches!( + *lifetime, + ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) + ) }) .count(); num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes } fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { - if let Some(Expr { + if let Some(&Expr { kind: ExprKind::Closure(&Closure { kind, body, .. }), .. }) = block.expr diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 1bd8813e348..fd71167f814 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -7,8 +7,7 @@ use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -53,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind && let BinOpKind::Mul = &bin_op.node - && !in_external_macro(cx.sess(), expr.span) + && !expr.span.from_expansion() && self.msrv.meets(msrvs::MANUAL_BITS) && let ctxt = expr.span.ctxt() && left_expr.span.ctxt() == ctxt diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs index c62bd65bea6..7a9c9963742 100644 --- a/clippy_lints/src/manual_hash_one.rs +++ b/clippy_lints/src/manual_hash_one.rs @@ -68,7 +68,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Some(init) = local.init && !init.span.from_expansion() && let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind - && seg.ident.name == sym!(build_hasher) + && seg.ident.name.as_str() == "build_hasher" && let Node::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id) && let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.hir_id) @@ -96,7 +96,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id) && !finish_expr.span.from_expansion() && let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind - && seg.ident.name == sym!(finish) + && seg.ident.name.as_str() == "finish" && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE) { diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 24207705743..dec8c5d85de 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { check_is_ascii(cx, macro_call.span, recv, &range, None); } } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind - && path.ident.name == sym!(contains) + && path.ident.name.as_str() == "contains" && let Some(higher::Range { start: Some(start), end: Some(end), diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 64cb7a06ce9..6d62b530ae7 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -148,7 +148,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option<bool> { }) => Some(*b), ExprKind::Block( rustc_hir::Block { - stmts: &[], + stmts: [], expr: Some(exp), .. }, diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 463aa602bc8..1267fc9d0a5 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; @@ -23,11 +25,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind() && let ty::Str = ty.kind() { - let mut visitor = MatchExprVisitor { cx, case_method: None }; - - visitor.visit_expr(scrutinee); - - if let Some(case_method) = visitor.case_method { + let mut visitor = MatchExprVisitor { cx }; + if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) { if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) { lint(cx, &case_method, bad_case_span, bad_case_sym.as_str()); } @@ -37,30 +36,33 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm struct MatchExprVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - case_method: Option<CaseMethod>, } impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - match ex.kind { - ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {}, - _ => walk_expr(self, ex), + type Result = ControlFlow<CaseMethod>; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { + if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind { + let result = self.case_altered(segment.ident.as_str(), receiver); + if result.is_break() { + return result; + } } + + walk_expr(self, ex) } } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool { + fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str { - self.case_method = Some(case_method); - return true; + return ControlFlow::Break(case_method); } } - false + ControlFlow::Continue(()) } } diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 42d9efe4ff6..9e54475033c 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp}; use rustc_lint::LateContext; use rustc_span::symbol::Ident; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, sym}; use std::borrow::Cow; use std::ops::ControlFlow; @@ -95,7 +95,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind && let Some(binding) = get_pat_binding(cx, recv, outer_arm) { - check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding); + check_method_calls(cx, outer_arm, path.ident.name.as_str(), recv, args, guard, &binding); } } } @@ -103,7 +103,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: fn check_method_calls<'tcx>( cx: &LateContext<'tcx>, arm: &Arm<'tcx>, - method: Symbol, + method: &str, recv: &Expr<'_>, args: &[Expr<'_>], if_expr: &Expr<'_>, @@ -112,7 +112,7 @@ fn check_method_calls<'tcx>( let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let slice_like = ty.is_slice() || ty.is_array(); - let sugg = if method == sym!(is_empty) { + let sugg = if method == "is_empty" { // `s if s.is_empty()` becomes "" // `arr if arr.is_empty()` becomes [] @@ -137,9 +137,9 @@ fn check_method_calls<'tcx>( if needles.is_empty() { sugg.insert_str(1, ".."); - } else if method == sym!(starts_with) { + } else if method == "starts_with" { sugg.insert_str(sugg.len() - 1, ", .."); - } else if method == sym!(ends_with) { + } else if method == "ends_with" { sugg.insert_str(1, ".., "); } else { return; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 9ca75fb2615..ca45ed6ea59 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -319,10 +319,9 @@ fn found_good_method<'tcx>( node: (&PatKind<'_>, &PatKind<'_>), ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { match node { - ( - PatKind::TupleStruct(ref path_left, patterns_left, _), - PatKind::TupleStruct(ref path_right, patterns_right, _), - ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _)) + if patterns_left.len() == 1 && patterns_right.len() == 1 => + { if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { find_good_method_for_match( cx, @@ -350,8 +349,8 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) - | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right)) + | (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _)) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { @@ -381,14 +380,14 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { get_good_method(cx, arms, path_left) } else { None } }, - (PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), + (PatKind::Path(path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), _ => None, } } diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 047d070a131..2aea25b5f12 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -247,7 +247,10 @@ impl<'a> PatState<'a> { let states = match self { Self::Wild => return None, Self::Other => { - *self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other))); + *self = Self::StdEnum( + cx.arena + .alloc_from_iter(std::iter::repeat_with(|| Self::Other).take(adt.variants().len())), + ); let Self::StdEnum(x) = self else { unreachable!(); }; diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 06482a743dd..c1653b65e98 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -21,8 +21,8 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name, ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, - ExprKind::Closure(&Closure { body, .. }) => { - let body = cx.tcx.hir().body(body); + ExprKind::Closure(Closure { body, .. }) => { + let body = cx.tcx.hir().body(*body); let closure_expr = peel_blocks(body.value); match closure_expr.kind { ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => { @@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) { + if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" { Some(Self::IsSome { receiver, side_effect_expr_span, }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) { + } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" { Some(Self::IsOk { receiver, side_effect_expr_span, diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index cc82f6cfd63..a0c21faaa4c 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{find_binding_init, path_to_local}; +use clippy_utils::macros::{is_assert_macro, root_macro_call}; +use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local}; use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; @@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_ if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) { return; } + if let Some(parent) = get_parent_expr(cx, expr) { + if let Some(parent) = get_parent_expr(cx, parent) { + if is_inside_always_const_context(cx.tcx, expr.hir_id) + && let Some(macro_call) = root_macro_call(parent.span) + && is_assert_macro(cx, macro_call.def_id) + { + return; + } + } + } let init_expr = expr_or_init(cx, receiver); if !receiver.span.eq_ctxt(init_expr.span) { return; diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 9d462bd1845..9a62b719a8f 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -29,10 +29,7 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { // For array::IntoIter<T, const N: usize>, the length is the second generic // parameter. - substs - .const_at(1) - .try_to_target_usize(cx.tcx) - .map(u128::from) + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) && let ExprKind::MethodCall(_, recv, ..) = iter.kind { diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints/src/methods/map_all_any_identity.rs new file mode 100644 index 00000000000..ac11baa2d54 --- /dev/null +++ b/clippy_lints/src/methods/map_all_any_identity.rs @@ -0,0 +1,43 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{is_expr_identity_function, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::{Span, sym}; + +use super::MAP_ALL_ANY_IDENTITY; + +#[allow(clippy::too_many_arguments)] +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + map_call_span: Span, + map_arg: &Expr<'_>, + any_call_span: Span, + any_arg: &Expr<'_>, + method: &str, +) { + if is_trait_method(cx, expr, sym::Iterator) + && is_trait_method(cx, recv, sym::Iterator) + && is_expr_identity_function(cx, any_arg) + && let map_any_call_span = map_call_span.with_hi(any_call_span.hi()) + && let Some(map_arg) = map_arg.span.get_source_text(cx) + { + span_lint_and_then( + cx, + MAP_ALL_ANY_IDENTITY, + map_any_call_span, + format!("usage of `.map(...).{method}(identity)`"), + |diag| { + diag.span_suggestion_verbose( + map_any_call_span, + format!("use `.{method}(...)` instead"), + format!("{method}({map_arg})"), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs new file mode 100644 index 00000000000..fc656fd78ba --- /dev/null +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -0,0 +1,134 @@ +use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; +use clippy_config::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eager_or_lazy, higher, usage}; +use rustc_ast::LitKind; +use rustc_ast::ast::RangeLimits; +use rustc_data_structures::packed::Pu128; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +fn extract_count_with_applicability( + cx: &LateContext<'_>, + range: higher::Range<'_>, + applicability: &mut Applicability, +) -> Option<String> { + let start = range.start?; + let end = range.end?; + // TODO: This doens't handle if either the start or end are negative literals, or if the start is + // not a literal. In the first case, we need to be careful about how we handle computing the + // count to avoid overflows. In the second, we may need to add parenthesis to make the + // suggestion correct. + if let ExprKind::Lit(lit) = start.kind + && let LitKind::Int(Pu128(lower_bound), _) = lit.node + { + if let ExprKind::Lit(lit) = end.kind + && let LitKind::Int(Pu128(upper_bound), _) = lit.node + { + // Here we can explicitly calculate the number of iterations + let count = if upper_bound >= lower_bound { + match range.limits { + RangeLimits::HalfOpen => upper_bound - lower_bound, + RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?, + } + } else { + 0 + }; + return Some(format!("{count}")); + } + let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) + .maybe_par() + .into_string(); + if lower_bound == 0 { + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} + 1")); + } + return Some(end_snippet); + } + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} - {}", lower_bound - 1)); + } + return Some(format!("{end_snippet} - {lower_bound}")); + } + None +} + +pub(super) fn check( + cx: &LateContext<'_>, + ex: &Expr<'_>, + receiver: &Expr<'_>, + arg: &Expr<'_>, + msrv: &Msrv, + method_call_span: Span, +) { + let mut applicability = Applicability::MaybeIncorrect; + if let Some(range) = higher::Range::hir(receiver) + && let ExprKind::Closure(Closure { body, .. }) = arg.kind + && let body_hir = cx.tcx.hir().body(*body) + && let Body { + params: [param], + value: body_expr, + } = body_hir + && !usage::BindingUsageFinder::are_params_used(cx, body_hir) + && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) + { + let method_to_use_name; + let new_span; + let use_take; + + if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { + if msrv.meets(msrvs::REPEAT_N) { + method_to_use_name = "repeat_n"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, format!("{body_snippet}, {count}")); + use_take = false; + } else { + method_to_use_name = "repeat"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, body_snippet.to_string()); + use_take = true; + } + } else if msrv.meets(msrvs::REPEAT_WITH) { + method_to_use_name = "repeat_with"; + new_span = (param.span, String::new()); + use_take = true; + } else { + return; + } + + // We need to provide nonempty parts to diag.multipart_suggestion so we + // collate all our parts here and then remove those that are empty. + let mut parts = vec![ + ( + receiver.span.to(method_call_span), + format!("std::iter::{method_to_use_name}"), + ), + new_span, + ]; + if use_take { + parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); + } + + span_lint_and_then( + cx, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + ex.span, + "map of a closure that does not depend on its parameter over a range", + |diag| { + diag.multipart_suggestion( + if use_take { + format!("remove the explicit range and use `{method_to_use_name}` and `take`") + } else { + format!("remove the explicit range and use `{method_to_use_name}`") + }, + parts, + applicability, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 2a391870d70..b1809796355 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -60,13 +60,16 @@ mod manual_ok_or; mod manual_saturating_arithmetic; mod manual_str_repeat; mod manual_try_fold; +mod map_all_any_identity; mod map_clone; mod map_collect_result_unit; mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod map_with_unused_argument_over_ranges; mod mut_mutex_lock; +mod needless_as_bytes; mod needless_character_iteration; mod needless_collect; mod needless_option_as_deref; @@ -4166,6 +4169,90 @@ declare_clippy_lint! { "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" } +declare_clippy_lint! { + /// ### What it does + /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. + /// + /// ### Why is this bad? + /// The `len()` and `is_empty()` methods are also directly available on strings, and they + /// return identical results. In particular, `len()` on a string returns the number of + /// bytes. + /// + /// ### Example + /// ``` + /// let len = "some string".as_bytes().len(); + /// let b = "some string".as_bytes().is_empty(); + /// ``` + /// Use instead: + /// ``` + /// let len = "some string".len(); + /// let b = "some string".is_empty(); + /// ``` + #[clippy::version = "1.84.0"] + pub NEEDLESS_AS_BYTES, + complexity, + "detect useless calls to `as_bytes()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. + /// + /// ### Why is this bad? + /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. + /// + /// ### Example + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); + /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); + /// ``` + /// Use instead: + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().all(|s| s.is_empty()); + /// let e2 = v.iter().any(|s| s.is_empty()); + /// ``` + #[clippy::version = "1.84.0"] + pub MAP_ALL_ANY_IDENTITY, + complexity, + "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::map` over ranges without using the parameter which + /// could be more clearly expressed using `std::iter::repeat(...).take(...)` + /// or `std::iter::repeat_n`. + /// + /// ### Why is this bad? + /// + /// It expresses the intent more clearly to `take` the correct number of times + /// from a generating function than to apply a closure to each number in a + /// range only to discard them. + /// + /// ### Example + /// + /// ```no_run + /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); + /// ``` + /// + /// ### Known Issues + /// + /// This lint may suggest replacing a `Map<Range>` with a `Take<RepeatWith>`. + /// The former implements some traits that the latter does not, such as + /// `DoubleEndedIterator`. + #[clippy::version = "1.84.0"] + pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + restriction, + "map of a trivial closure (not dependent on parameter) over a range" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [ NEEDLESS_CHARACTER_ITERATION, MANUAL_INSPECT, UNNECESSARY_MIN_OR_MAX, + NEEDLESS_AS_BYTES, + MAP_ALL_ANY_IDENTITY, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4534,15 +4624,21 @@ impl Methods { ("all", [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); - if let Some(("cloned", recv2, [], _, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ); + match method_call(recv) { + Some(("cloned", recv2, [], _, _)) => { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ); + }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all"); + }, + _ => {}, } }, ("and_then", [arg]) => { @@ -4571,6 +4667,9 @@ impl Methods { { string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any"); + }, _ => {}, } }, @@ -4764,8 +4863,14 @@ impl Methods { unit_hash::check(cx, expr, recv, arg); }, ("is_empty", []) => { - if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { - redundant_as_str::check(cx, expr, recv, as_str_span, span); + match method_call(recv) { + Some(("as_bytes", prev_recv, [], _, _)) => { + needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span); + }, + Some(("as_str", recv, [], as_str_span, _)) => { + redundant_as_str::check(cx, expr, recv, as_str_span, span); + }, + _ => {}, } is_empty::check(cx, expr, recv); }, @@ -4795,6 +4900,11 @@ impl Methods { ); } }, + ("len", []) => { + if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) { + needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span); + } + }, ("lock", []) => { mut_mutex_lock::check(cx, expr, recv, span); }, @@ -4802,6 +4912,7 @@ impl Methods { if name == "map" { unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, &self.msrv); + map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span); match method_call(recv) { Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); @@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase { } #[rustfmt::skip] -#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs new file mode 100644 index 00000000000..75e9f317230 --- /dev/null +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -0,0 +1,28 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::is_type_lang_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, LangItem}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::NEEDLESS_AS_BYTES; + +pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) { + if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() + && let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs() + && (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str()) + { + let mut app = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + NEEDLESS_AS_BYTES, + span, + "needless call to `as_bytes()`", + format!("`{method}()` can be called directly on strings"), + format!("{sugg}.{method}()"), + app, + ); + } +} diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 96a31812ca4..055107068ae 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { // Check function calls on our collection if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { if args.is_empty() - && method_name.ident.name == sym!(collect) + && method_name.ident.name.as_str() == "collect" && is_trait_method(self.cx, expr, sym::Iterator) { self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 65e545ed03a..db2b9d4d92f 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< if let Some(parent) = get_parent_expr(cx, expr) { let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind { if args.is_empty() - && segment.ident.name == sym!(parse) + && segment.ident.name.as_str() == "parse" && let parse_result_ty = cx.typeck_results().expr_ty(parent) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) && let ty::Adt(_, substs) = parse_result_ty.kind() @@ -58,7 +58,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< "calling `.parse()` on a string without trimming the trailing newline character", "checking", )) - } else if segment.ident.name == sym!(ends_with) + } else if segment.ident.name.as_str() == "ends_with" && recv.span == expr.span && let [arg] = args && expr_is_string_literal_without_trailing_newline(arg) diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index ca46da81fa0..bab439015c5 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,13 +1,15 @@ use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_copy; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; +use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; use core::ops::ControlFlow; +use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; +use rustc_middle::query::Key; use rustc_middle::ty; use rustc_span::sym; @@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a ControlFlow::Continue(Descend::Yes) } }); - let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { + // Check if the closure is .filter_map(|x| Some(x)) + if name == "filter_map" + && let hir::ExprKind::Call(expr, args) = body.value.kind + && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome) + && arg_id.ty_def_id() == args[0].hir_id().ty_def_id() + && let hir::ExprKind::Path(_) = args[0].kind + { + span_lint_and_sugg( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + format!("{name} is unnecessary"), + "try removing the filter_map", + String::new(), + Applicability::MaybeIncorrect, + ); + } if name == "filter_map" { "map" } else { "map(..).next()" } } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(body.value).kind() { @@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a } else { return; }; - span_lint( + span_lint_and_sugg( cx, if name == "filter_map" { UNNECESSARY_FILTER_MAP @@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a UNNECESSARY_FIND_MAP }, expr.span, - format!("this `.{name}` can be written more simply using `.{sugg}`"), + format!("this `.{name}` can be written more simply"), + "try instead", + sugg.to_string(), + Applicability::MaybeIncorrect, ); } } @@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc (true, true) }, hir::ExprKind::MethodCall(segment, recv, [arg], _) => { - if segment.ident.name == sym!(then_some) + if segment.ident.name.as_str() == "then_some" && cx.typeck_results().expr_ty(recv).is_bool() && path_to_local_id(arg, arg_id) { diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index e95864c6db8..ed89b3b3438 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -79,9 +79,9 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM }, ExprKind::MethodCall(path, receiver, args @ [_], _) => { if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { - if path.ident.name == sym!(max) { + if path.ident.name.as_str() == "max" { fetch_const(cx, Some(receiver), args, MinMax::Max) - } else if path.ident.name == sym!(min) { + } else if path.ident.name.as_str() == "min" { fetch_const(cx, Some(receiver), args, MinMax::Min) } else { None diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index ce508d85d63..1300c7d1062 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -12,11 +12,13 @@ use clippy_utils::is_from_proc_macro; use clippy_utils::source::SpanRangeExt; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; +use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Visibility; use rustc_session::impl_lint_pass; use rustc_span::def_id::CRATE_DEF_ID; +use rustc_span::symbol::kw; use rustc_span::{Span, sym}; declare_clippy_lint! { @@ -110,6 +112,21 @@ impl MissingDoc { return; } + if let Some(parent_def_id) = cx.tcx.opt_parent(def_id.to_def_id()) + && let DefKind::AnonConst + | DefKind::AssocConst + | DefKind::AssocFn + | DefKind::Closure + | DefKind::Const + | DefKind::Fn + | DefKind::InlineConst + | DefKind::Static { .. } + | DefKind::SyntheticCoroutineBody = cx.tcx.def_kind(parent_def_id) + { + // Nested item has no generated documentation, so it doesn't need to be documented. + return; + } + let has_doc = attrs .iter() .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())) @@ -184,8 +201,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { } } }, - hir::ItemKind::Const(..) - | hir::ItemKind::Enum(..) + hir::ItemKind::Const(..) => { + if it.ident.name == kw::Underscore { + note_prev_span_then_ret!(self.prev_span, it.span); + } + }, + hir::ItemKind::Enum(..) | hir::ItemKind::Macro(..) | hir::ItemKind::Mod(..) | hir::ItemKind::Static(..) diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index fc01b6753f1..f28b431ab99 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -116,7 +116,7 @@ fn should_lint<'tcx>( if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) { has_debug_struct = true; - } else if path.ident.name == sym!(finish_non_exhaustive) + } else if path.ident.name.as_str() == "finish_non_exhaustive" && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct) { has_finish_non_exhaustive = true; diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index ce97370d4d9..c4836772933 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -330,10 +330,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin } fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind + if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind && let Some(last_stmt) = loop_block.stmts.last() && let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind - && let ast::ExprKind::Continue(_) = inner_expr.kind + && let ast::ExprKind::Continue(continue_label) = inner_expr.kind + && compare_labels(loop_label.as_ref(), continue_label.as_ref()) { span_lint_and_help( cx, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 46cdb82130f..a67addea948 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -347,7 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { if let LetStmt { init: None, pat: - &Pat { + Pat { kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None), .. }, @@ -357,7 +357,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { && let Some((_, Node::Stmt(local_stmt))) = parents.next() && let Some((_, Node::Block(block))) = parents.next() { - check(cx, local, local_stmt, block, binding_id); + check(cx, local, local_stmt, block, *binding_id); } } } diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index 9a1c397b5b2..852a0ce8c6d 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::def_id::{DefId, DefIdMap}; -use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, BoundPolarity, WherePredicate}; +use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicate}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ClauseKind, PredicatePolarity}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index f8c815d9729..b7dc269061c 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -183,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { .iter() .zip(fn_sig.inputs()) .zip(body.params) - .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) + .filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) .peekable(); if it.peek().is_none() { return; diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 2e5195d459f..74536028b5d 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -159,8 +159,12 @@ impl NoEffect { // Remove `impl Future<Output = T>` to get `T` if cx.tcx.ty_is_opaque_future(ret_ty) - && let Some(true_ret_ty) = - cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty) + && let Some(true_ret_ty) = cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .get_impl_future_output_ty(ret_ty) { ret_ty = true_ret_ty; } diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 8d5a523fd8f..64587e08ddc 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -18,13 +18,13 @@ declare_clippy_lint! { /// Rust ABI can break this at any point. /// /// ### Example - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// fn example(arg_one: u32, arg_two: usize) {} /// ``` /// /// Use instead: - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// extern "C" fn example(arg_one: u32, arg_two: usize) {} /// ``` @@ -40,24 +40,25 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { if let ItemKind::Fn(fn_sig, _, _) = &item.kind { let attrs = cx.tcx.hir().attrs(item.hir_id()); let mut app = Applicability::MaybeIncorrect; - let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app); + let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(item.ident.span.lo()), "..", &mut app); for attr in attrs { if let Some(ident) = attr.ident() && ident.name == rustc_span::sym::no_mangle && fn_sig.header.abi == Abi::Rust - && let Some((fn_attrs, _)) = snippet.split_once("fn") + && let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn") && !fn_attrs.contains("extern") { let sugg_span = fn_sig .span .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) .shrink_to_lo(); + let attr_snippet = snippet(cx, attr.span, ".."); span_lint_and_then( cx, NO_MANGLE_WITH_RUST_ABI, fn_sig.span, - "`#[no_mangle]` set on a function with the default (`Rust`) ABI", + format!("`{attr_snippet}` set on a function with the default (`Rust`) ABI"), |diag| { diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 3f156aa5510..0caa19cd844 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,12 +43,12 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym!(mode) + && ((path.ident.name.as_str() == "mode" && matches!( cx.tcx.get_diagnostic_name(adt.did()), Some(sym::FsOpenOptions | sym::DirBuilder) )) - || (path.ident.name == sym!(set_mode) + || (path.ident.name.as_str() == "set_mode" && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index 8e394944c21..ab5f91c1d67 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -107,7 +107,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind - && sym!(signum) == method_name.ident.name + && method_name.ident.name.as_str() == "signum" // Check that the receiver of the signum() is a float (expressions[0] is the receiver of // the method call) { diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 18e6aad9c9a..794bef7b321 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -34,7 +34,7 @@ declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]); impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: impl_items, .. }) = item.kind diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs index 56d07aeae17..808a7e005c6 100644 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ b/clippy_lints/src/ptr_offset_with_cast.rs @@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>( if path_segment.ident.name == sym::offset { return Some((arg_0, arg_1, Method::Offset)); } - if path_segment.ident.name == sym!(wrapping_offset) { + if path_segment.ident.name.as_str() == "wrapping_offset" { return Some((arg_0, arg_1, Method::WrappingOffset)); } } diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 9344cb41993..cb374fcf20e 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -93,7 +93,7 @@ enum IfBlockType<'hir> { fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> { if let Block { - stmts: &[], + stmts: [], expr: Some(els), .. } = block @@ -163,8 +163,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ is_type_diagnostic_item(cx, caller_ty, smbl) && expr_return_none_or_err(smbl, cx, if_then, caller, None) && match smbl { - sym::Option => call_sym == sym!(is_none), - sym::Result => call_sym == sym!(is_err), + sym::Option => call_sym.as_str() == "is_none", + sym::Result => call_sym.as_str() == "is_err", _ => false, } }, diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 19ce08bde10..7d59bf24d93 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef { fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) { if let TyKind::Ref(_, ref mut_ty) = ty.kind && mut_ty.mutbl == Mutability::Not - && let TyKind::Path(ref qpath) = &mut_ty.ty.kind + && let TyKind::Path(qpath) = &mut_ty.ty.kind && let last = last_path_segment(qpath) && let Some(def_id) = last.res.opt_def_id() && cx.tcx.is_diagnostic_item(sym::Option, def_id) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 110dea8fb8e..1e0f6dff1ab 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -347,7 +347,7 @@ fn check_final_expr<'tcx>( let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" - ExprKind::Ret(ref inner) => { + ExprKind::Ret(inner) => { // check if expr return nothing let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 09f1c112352..d85f4a8fa01 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -101,17 +101,21 @@ impl SemicolonBlock { ); } - fn semicolon_outside_block( - &self, - cx: &LateContext<'_>, - block: &Block<'_>, - tail_stmt_expr: &Expr<'_>, - semi_span: Span, - ) { + fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) { let insert_span = block.span.with_lo(block.span.hi()); - // account for macro calls - let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span); - let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi()); + + // For macro call semicolon statements (`mac!();`), the statement's span does not actually + // include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon + // based on a source snippet. + // (Does not use `stmt_span` as that requires `.from_expansion()` to return true, + // which is not the case for e.g. `line!();` and `asm!();`) + let Some(remove_span) = cx + .sess() + .source_map() + .mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite()) + else { + return; + }; if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) { return; @@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock { }; let &Stmt { kind: StmtKind::Semi(expr), - span, .. } = stmt else { return; }; - self.semicolon_outside_block(cx, block, expr, span); + self.semicolon_outside_block(cx, block, expr); }, StmtKind::Semi(Expr { kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _), diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index fcf1c4cc5c2..6a0dfde2d9c 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -26,7 +26,7 @@ declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]); impl<'tcx> LateLintPass<'tcx> for SerdeApi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items, .. }) = item.kind diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 9fdee8543a8..fa082453504 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -174,7 +174,7 @@ impl SingleComponentPathImports { } match &item.kind { - ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { + ItemKind::Mod(_, ModKind::Loaded(items, ..)) => { self.check_mod(items); }, ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index ec6e45256d1..8c8f11569c8 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -235,7 +235,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(extend) + && path.ident.name.as_str() == "extend" && self.is_repeat_take(extend_arg) { self.slow_expression = Some(InitializationType::Extend(expr)); @@ -247,7 +247,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(resize) + && path.ident.name.as_str() == "resize" // Check that is filled with 0 && is_integer_literal(fill_arg, 0) { @@ -269,7 +269,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { /// Returns `true` if give expression is `repeat(0).take(...)` fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind - && take_path.ident.name == sym!(take) + && take_path.ident.name.as_str() == "take" // Check that take is applied to `repeat(0)` && self.is_repeat_zero(recv) { diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index bf49bb60162..e09c0706006 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -286,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { if !in_external_macro(cx.sess(), e.span) && let ExprKind::MethodCall(path, receiver, ..) = &e.kind - && path.ident.name == sym!(as_bytes) + && path.ident.name.as_str() == "as_bytes" && let ExprKind::Lit(lit) = &receiver.kind && let LitKind::Str(lit_content, _) = &lit.node { @@ -332,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { } if let ExprKind::MethodCall(path, recv, [], _) = &e.kind - && path.ident.name == sym!(into_bytes) + && path.ident.name.as_str() == "into_bytes" && let ExprKind::MethodCall(path, recv, [], _) = &recv.kind && matches!(path.ident.name.as_str(), "to_owned" | "to_string") && let ExprKind::Lit(lit) = &recv.kind @@ -493,7 +493,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { let tyckres = cx.typeck_results(); if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind - && path.ident.name == sym!(split_whitespace) + && path.ident.name.as_str() == "split_whitespace" && let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id) && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index be32dc0aab0..0d809c17989 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -334,7 +334,7 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { let mut output = expr; loop { output = match &output.kind { - ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, + ExprKind::Paren(inner) | ExprKind::Unary(_, inner) => inner, _ => { return output; }, @@ -348,13 +348,13 @@ fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { fn if_statement_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { match kind { - ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), - ExprKind::Paren(ref e) => if_statement_binops(&e.kind), - ExprKind::Block(ref block, _) => { + ExprKind::If(condition, _, _) => chained_binops(&condition.kind), + ExprKind::Paren(e) => if_statement_binops(&e.kind), + ExprKind::Block(block, _) => { let mut output = None; for stmt in &block.stmts { - match stmt.kind { - StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { + match &stmt.kind { + StmtKind::Expr(e) | StmtKind::Semi(e) => { output = append_opt_vecs(output, if_statement_binops(&e.kind)); }, _ => {}, @@ -383,7 +383,7 @@ fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { match kind { ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), - ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind), + ExprKind::Paren(e) | ExprKind::Unary(_, e) => chained_binops(&e.kind), _ => None, } } @@ -391,16 +391,14 @@ fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> { match (&left_outer.kind, &right_outer.kind) { ( - ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), - ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), + ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), + ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e), ) => chained_binops_helper(left_e, right_e), - (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer), - (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { - chained_binops_helper(left_outer, right_e) - }, + (ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), _) => chained_binops_helper(left_e, right_outer), + (_, ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e)) => chained_binops_helper(left_outer, right_e), ( - ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right), - ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), + ExprKind::Binary(Spanned { node: left_op, .. }, left_left, left_right), + ExprKind::Binary(Spanned { node: right_op, .. }, right_left, right_right), ) => match ( chained_binops_helper(left_left, left_right), chained_binops_helper(right_left, right_right), diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 6cba560393d..23362506cca 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -392,7 +392,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> { for stmt in self.block.stmts { match stmt.kind { StmtKind::Expr(expr) | StmtKind::Semi(expr) => v.visit_expr(expr), - StmtKind::Let(LetStmt { ref init, .. }) => { + StmtKind::Let(LetStmt { init, .. }) => { if let Some(init) = init.as_ref() { v.visit_expr(init); } diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 3da4bf67558..07bf4319ff0 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -10,8 +10,8 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, - TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, BoundPolarity, + BoundPolarity, GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, + TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { let mut self_bounds_map = FxHashMap::default(); for predicate in item.generics.predicates { - if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate + if let WherePredicate::BoundPredicate(bound_predicate) = predicate && bound_predicate.origin != PredicateOrigin::ImplTrait && !bound_predicate.span.from_expansion() && let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind @@ -268,7 +268,7 @@ impl TraitBounds { let mut map: UnhashMap<SpanlessTy<'_, '_>, Vec<&GenericBound<'_>>> = UnhashMap::default(); let mut applicability = Applicability::MaybeIncorrect; for bound in generics.predicates { - if let WherePredicate::BoundPredicate(ref p) = bound + if let WherePredicate::BoundPredicate(p) = bound && p.origin != PredicateOrigin::ImplTrait && p.bounds.len() as u64 <= self.max_trait_bounds && !p.span.from_expansion() @@ -379,7 +379,7 @@ struct ComparableTraitRef<'a, 'tcx> { impl PartialEq for ComparableTraitRef<'_, '_> { fn eq(&self, other: &Self) -> bool { - SpanlessEq::new(self.cx).eq_modifiers(self.modifiers, other.modifiers) + SpanlessEq::eq_modifiers(self.modifiers, other.modifiers) && SpanlessEq::new(self.cx) .paths_by_resolution() .eq_path(self.trait_ref.path, other.trait_ref.path) diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 1dfc9f7091e..ca9daf2d2a0 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -43,7 +43,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_ binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs) }, ExprKind::MethodCall(path, receiver, [arg], _) - if path.ident.name == sym!(contains) + if path.ident.name.as_str() == "contains" // ... `contains` called on some kind of range && let Some(receiver_adt) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() && let lang_items = cx.tcx.lang_items() @@ -81,7 +81,7 @@ pub(super) fn check<'tcx>( if let Some(then_some_call) = peel_parent_unsafe_blocks(cx, expr) && let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind && cx.typeck_results().expr_ty(receiver).is_bool() - && path.ident.name == sym!(then_some) + && path.ident.name.as_str() == "then_some" && is_local_with_projections(transmutable) && binops_with_local(cx, transmutable, receiver) && is_normalizable(cx, cx.param_env, from_ty) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 44eb0a6c593..0bba611116b 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -295,7 +295,7 @@ fn expr_has_unnecessary_safety_comment<'tcx>( if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| { matches!( node, - Node::Block(&Block { + Node::Block(Block { rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), .. }), diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index bf2d75ea1a9..1a1284ce9c4 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -194,7 +194,7 @@ fn needs_inferred_result_ty( let (id, receiver, args) = match e.kind { ExprKind::Call( Expr { - kind: ExprKind::Path(ref path), + kind: ExprKind::Path(path), hir_id, .. }, diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index e7d26fa238e..0f4bd286145 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -149,7 +149,7 @@ fn is_empty_block(expr: &Expr<'_>) -> bool { expr.kind, ExprKind::Block( Block { - stmts: &[], + stmts: [], expr: None, .. }, diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index cf406b817da..7c9455bf8ab 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks}; +use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -83,6 +83,28 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { /// to consider the arms, and we want to avoid breaking the logic for situations where things /// get desugared to match. fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { + let fn_def_id = block.hir_id.owner.to_def_id(); + if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id) + && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id) + { + // We don't want to lint inside io::Read or io::Write implementations, as the author has more + // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 + if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + return; + } + + let async_paths: [&[&str]; 4] = [ + &paths::TOKIO_IO_ASYNCREADEXT, + &paths::TOKIO_IO_ASYNCWRITEEXT, + &paths::FUTURES_IO_ASYNCREADEXT, + &paths::FUTURES_IO_ASYNCWRITEEXT, + ]; + + if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) { + return; + } + } + for stmt in block.stmts { if let hir::StmtKind::Semi(exp) = stmt.kind { check_expr(cx, exp); @@ -222,7 +244,7 @@ fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { } fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - while let ExprKind::Call(func, [ref arg_0]) = expr.kind + while let ExprKind::Call(func, [arg_0]) = expr.kind && matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..)) @@ -244,7 +266,7 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { /// waited on. Otherwise return None. fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind { - if let ExprKind::Call(func, [ref arg_0]) = expr.kind { + if let ExprKind::Call(func, [arg_0]) = expr.kind { if matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) diff --git a/clippy_lints/src/utils/attr_collector.rs b/clippy_lints/src/utils/attr_collector.rs new file mode 100644 index 00000000000..1522553bbf5 --- /dev/null +++ b/clippy_lints/src/utils/attr_collector.rs @@ -0,0 +1,40 @@ +use std::mem; +use std::sync::OnceLock; + +use rustc_ast::{Attribute, Crate}; +use rustc_data_structures::sync::Lrc; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::Span; + +#[derive(Clone, Default)] +pub struct AttrStorage(pub Lrc<OnceLock<Vec<Span>>>); + +pub struct AttrCollector { + storage: AttrStorage, + attrs: Vec<Span>, +} + +impl AttrCollector { + pub fn new(storage: AttrStorage) -> Self { + Self { + storage, + attrs: Vec::new(), + } + } +} + +impl_lint_pass!(AttrCollector => []); + +impl EarlyLintPass for AttrCollector { + fn check_attribute(&mut self, _cx: &EarlyContext<'_>, attr: &Attribute) { + self.attrs.push(attr.span); + } + + fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) { + self.storage + .0 + .set(mem::take(&mut self.attrs)) + .expect("should only be called once"); + } +} diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 31f9d84f5e4..1bf24083665 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -396,7 +396,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.pat(field!(let_expr.pat)); // Does what ExprKind::Cast does, only adds a clause for the type // if it's a path - if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { + if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { bind!(self, qpath); chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind"); self.qpath(qpath); diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index f662c7651f6..deb983b6971 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -6,5 +6,6 @@ pub mod lint_without_lint_pass; pub mod msrv_attr_impl; pub mod outer_expn_data_pass; pub mod produce_ice; +pub mod slow_symbol_comparisons; pub mod unnecessary_def_path; pub mod unsorted_clippy_utils_paths; diff --git a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs index a3f9abe4f96..eaeb754a23f 100644 --- a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs +++ b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind && is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]) - && let ExprKind::Closure(&Closure { body, .. }) = &call_f.kind + && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind && let body = cx.tcx.hir().body(body) && let only_expr = peel_blocks_with_stmt(body.value) && let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind diff --git a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs index 51235de9f29..af38e066559 100644 --- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not + /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not /// know the name of the lint. /// /// ### Known problems @@ -123,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { .expect("lints must have a description field"); if let ExprKind::Lit(Spanned { - node: LitKind::Str(ref sym, _), + node: LitKind::Str(sym, _), .. }) = field.expr.kind { @@ -159,8 +159,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { let body = cx.tcx.hir().body_owned_by( impl_item_refs .iter() - .find(|iiref| iiref.ident.as_str() == "get_lints") - .expect("LintPass needs to implement get_lints") + .find(|iiref| iiref.ident.as_str() == "lint_vec") + .expect("LintPass needs to implement lint_vec") .id .owner_id .def_id, @@ -218,9 +218,7 @@ pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) { if let Some(value) = extract_clippy_version_value(cx, item) { - // The `sym!` macro doesn't work as it only expects a single token. - // It's better to keep it this way and have a direct `Symbol::intern` call here. - if value == Symbol::intern("pre 1.29.0") { + if value.as_str() == "pre 1.29.0" { return; } @@ -251,7 +249,7 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<' pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option<Symbol> { let attrs = cx.tcx.hir().attrs(item.hir_id()); attrs.iter().find_map(|attr| { - if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind + if let ast::AttrKind::Normal(attr_kind) = &attr.kind // Identify attribute && let [tool_name, attr_name] = &attr_kind.item.path.segments[..] && tool_name.ident.name == sym::clippy diff --git a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs index 63fcbd61528..68692246153 100644 --- a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs +++ b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs @@ -42,7 +42,7 @@ impl LateLintPass<'_> for MsrvAttrImpl { .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_))) .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV)) }) - && !items.iter().any(|item| item.ident.name == sym!(check_attributes)) + && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") { let context = if is_late_pass { "LateContext" } else { "EarlyContext" }; let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" }; diff --git a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs new file mode 100644 index 00000000000..3742be0e103 --- /dev/null +++ b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs @@ -0,0 +1,69 @@ +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::match_type; +use clippy_utils::{match_function_call, paths}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// + /// Detects symbol comparision using `Symbol::intern`. + /// + /// ### Why is this bad? + /// + /// Comparision via `Symbol::as_str()` is faster if the interned symbols are not reused. + /// + /// ### Example + /// + /// None, see suggestion. + pub SLOW_SYMBOL_COMPARISONS, + internal, + "detects slow comparisions of symbol" +} + +declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]); + +fn check_slow_comparison<'tcx>( + cx: &LateContext<'tcx>, + op1: &'tcx Expr<'tcx>, + op2: &'tcx Expr<'tcx>, +) -> Option<(Span, String)> { + if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL) + && let Some([symbol_name_expr]) = match_function_call(cx, op2, &paths::SYMBOL_INTERN) + && let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr) + { + Some((op1.span, symbol_name)) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for SlowSymbolComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Binary(op, left, right) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + && let Some((symbol_span, symbol_name)) = + check_slow_comparison(cx, left, right).or_else(|| check_slow_comparison(cx, right, left)) + { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + SLOW_SYMBOL_COMPARISONS, + expr.span, + "comparing `Symbol` via `Symbol::intern`", + "use `Symbol::as_str` and check the string instead", + format!( + "{}.as_str() {} \"{symbol_name}\"", + snippet_with_applicability(cx, symbol_span, "symbol", &mut applicability), + op.node.as_str() + ), + applicability, + ); + }; + } +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 13e9ead9a57..4476cd1005e 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1,5 +1,7 @@ +pub mod attr_collector; pub mod author; pub mod dump_hir; pub mod format_args_collector; + #[cfg(feature = "internal")] pub mod internal_lints; |
