diff options
| author | Philipp Krones <hello@philkrones.com> | 2025-04-22 16:10:59 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2025-04-22 18:24:43 +0200 |
| commit | ff428d91c2b690b8dbd8cc1e48274870c24fe1e2 (patch) | |
| tree | 496897def77d0f71b6db9713a16c2ea4f5886f23 /clippy_lints/src | |
| parent | ed892e72dd05c6dbbd425343f84d1c1d348a898d (diff) | |
Merge commit '0621446356e20fd2ead13a6763bb936c95eb0cfa' into clippy-subtree-update
Diffstat (limited to 'clippy_lints/src')
237 files changed, 4243 insertions, 4757 deletions
diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 57cabe43703..272444475c0 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -5,6 +5,7 @@ use clippy_config::types::{ SourceItemOrderingWithinModuleItemGroupings, }; use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::is_cfg_test; use rustc_hir::{ AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, Variant, VariantData, @@ -263,10 +264,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { 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); - } + if let Some(cur_v) = cur_v + && 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); } @@ -278,10 +280,11 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { 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); - } + if let Some(cur_f) = cur_f + && 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); } @@ -342,7 +345,7 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { struct CurItem<'a> { item: &'a Item<'a>, order: usize, - name: String, + name: Option<String>, } let mut cur_t: Option<CurItem<'_>> = None; @@ -359,32 +362,36 @@ impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { // as no sorting by source map/line of code has to be applied. // for item in items { - if item.span.in_external_macro(cx.sess().source_map()) { + if is_cfg_test(cx.tcx, item.hir_id()) { continue; } - let ident = if let Some(ident) = item.kind.ident() { - ident - } else if let ItemKind::Impl(_) = item.kind - && !get_item_name(item).is_empty() - { - rustc_span::Ident::empty() // FIXME: a bit strange, is there a better way to do it? - } else { - continue; - }; - - if ident.name.as_str().starts_with('_') { - // Filters out unnamed macro-like impls for various derives, - // e.g. serde::Serialize or num_derive::FromPrimitive. + if item.span.in_external_macro(cx.sess().source_map()) { continue; } - if ident.name == rustc_span::sym::std && item.span.is_dummy() { - if let ItemKind::ExternCrate(None, _) = item.kind { - // Filters the auto-included Rust standard library. + if let Some(ident) = item.kind.ident() { + if ident.name.as_str().starts_with('_') { + // Filters out unnamed macro-like impls for various derives, + // e.g. serde::Serialize or num_derive::FromPrimitive. continue; } - println!("Unknown item: {item:?}"); + + if 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; + } + if cfg!(debug_assertions) { + rustc_middle::bug!("unknown item: {item:?}"); + } + } + } else if let ItemKind::Impl(_) = item.kind + && get_item_name(item).is_some() + { + // keep going below + } else { + continue; } let item_kind = convert_module_item_kind(&item.kind); @@ -493,7 +500,7 @@ fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleIte /// 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 { +fn get_item_name(item: &Item<'_>) -> Option<String> { match item.kind { ItemKind::Impl(im) => { if let TyKind::Path(path) = im.self_ty.kind { @@ -513,27 +520,19 @@ fn get_item_name(item: &Item<'_>) -> String { } segs.push(String::new()); - segs.join("!!") + Some(segs.join("!!")) }, QPath::TypeRelative(_, _path_seg) => { // This case doesn't exist in the clippy tests codebase. - String::new() + None }, - QPath::LangItem(_, _) => String::new(), + QPath::LangItem(_, _) => None, } } else { // Impls for anything that isn't a named type can be skipped. - String::new() + None } }, - // FIXME: `Ident::empty` for anonymous items is a bit strange, is there - // a better way to do it? - _ => item - .kind - .ident() - .unwrap_or(rustc_span::Ident::empty()) - .name - .as_str() - .to_owned(), + _ => item.kind.ident().map(|name| name.as_str().to_owned()), } } diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index 78102772927..27e304a848e 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -12,17 +12,17 @@ declare_clippy_lint! { /// regardless of whether good alternatives exist or not. If you want more /// precise lints for `as`, please consider using these separate lints: /// - /// - clippy::cast_lossless - /// - clippy::cast_possible_truncation - /// - clippy::cast_possible_wrap - /// - clippy::cast_precision_loss - /// - clippy::cast_sign_loss - /// - clippy::char_lit_as_u8 - /// - clippy::fn_to_numeric_cast - /// - clippy::fn_to_numeric_cast_with_truncation - /// - clippy::ptr_as_ptr - /// - clippy::unnecessary_cast - /// - invalid_reference_casting + /// - `clippy::cast_lossless` + /// - `clippy::cast_possible_truncation` + /// - `clippy::cast_possible_wrap` + /// - `clippy::cast_precision_loss` + /// - `clippy::cast_sign_loss` + /// - `clippy::char_lit_as_u8` + /// - `clippy::fn_to_numeric_cast` + /// - `clippy::fn_to_numeric_cast_with_truncation` + /// - `clippy::ptr_as_ptr` + /// - `clippy::unnecessary_cast` + /// - `invalid_reference_casting` /// /// There is a good explanation the reason why this lint should work in this /// way and how it is useful [in this diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index e5439a6d401..9acff676d4f 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -243,7 +243,7 @@ fn build_sugg<'tcx>( // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` Sugg::hir_with_applicability(cx, lhs, "_", app) } - .maybe_par(); + .maybe_paren(); // Determine whether we need to reference the argument to clone_from(). let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg); @@ -284,7 +284,7 @@ fn build_sugg<'tcx>( let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { // `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)` // `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)` - let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par(); + let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_paren(); let inner_type = cx.typeck_results().expr_ty(ref_expr); // If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it // deref to a mutable reference. @@ -296,7 +296,7 @@ fn build_sugg<'tcx>( } else { // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)` // `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)` - Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr() + Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_paren().mut_addr() }; match call_kind { diff --git a/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs b/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs index fecf3166406..457692ed5dc 100644 --- a/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs +++ b/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs @@ -8,17 +8,18 @@ use rustc_span::{DUMMY_SP, sym}; pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner]) { for lint in items { - if let Some(lint_name) = extract_clippy_lint(lint) { - if lint_name.as_str() == "restriction" && name != sym::allow { - span_lint_and_help( - cx, - BLANKET_CLIPPY_RESTRICTION_LINTS, - lint.span(), - "`clippy::restriction` is not meant to be enabled as a group", - None, - "enable the restriction lints you need individually", - ); - } + if let Some(lint_name) = extract_clippy_lint(lint) + && lint_name.as_str() == "restriction" + && name != sym::allow + { + span_lint_and_help( + cx, + BLANKET_CLIPPY_RESTRICTION_LINTS, + lint.span(), + "`clippy::restriction` is not meant to be enabled as a group", + None, + "enable the restriction lints you need individually", + ); } } } diff --git a/clippy_lints/src/attrs/deprecated_semver.rs b/clippy_lints/src/attrs/deprecated_semver.rs index d3153ec6613..50943b36809 100644 --- a/clippy_lints/src/attrs/deprecated_semver.rs +++ b/clippy_lints/src/attrs/deprecated_semver.rs @@ -6,10 +6,10 @@ use rustc_span::Span; use semver::Version; pub(super) fn check(cx: &EarlyContext<'_>, span: Span, lit: &MetaItemLit) { - if let LitKind::Str(is, _) = lit.kind { - if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() { - return; - } + if let LitKind::Str(is, _) = lit.kind + && (is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok()) + { + return; } span_lint( cx, diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs index 4c84e61b1f2..a851daaede7 100644 --- a/clippy_lints/src/attrs/duplicated_attributes.rs +++ b/clippy_lints/src/attrs/duplicated_attributes.rs @@ -36,10 +36,7 @@ fn check_duplicated_attr( } let Some(ident) = attr.ident() else { return }; let name = ident.name; - if name == sym::doc - || name == sym::cfg_attr_trace - || name == sym::rustc_on_unimplemented - || name == sym::reason { + if name == sym::doc || name == sym::cfg_attr_trace || name == sym::rustc_on_unimplemented || name == sym::reason { // FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg // conditions are the same. // `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected. diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index e04d2ad5d13..f7f168cb267 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -14,8 +14,9 @@ mod useless_attribute; mod utils; use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::msrvs::{self, Msrv, MsrvStack}; -use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind}; +use rustc_ast::{self as ast, AttrArgs, AttrKind, Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, ItemKind, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -448,6 +449,31 @@ declare_clippy_lint! { "duplicated attribute" } +declare_clippy_lint! { + /// ### What it does + /// Checks for ignored tests without messages. + /// + /// ### Why is this bad? + /// The reason for ignoring the test may not be obvious. + /// + /// ### Example + /// ```no_run + /// #[test] + /// #[ignore] + /// fn test() {} + /// ``` + /// Use instead: + /// ```no_run + /// #[test] + /// #[ignore = "Some good reason"] + /// fn test() {} + /// ``` + #[clippy::version = "1.85.0"] + pub IGNORE_WITHOUT_REASON, + pedantic, + "ignored tests without messages" +} + pub struct Attributes { msrv: Msrv, } @@ -532,6 +558,7 @@ impl_lint_pass!(PostExpansionEarlyAttributes => [ ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON, DEPRECATED_SEMVER, + IGNORE_WITHOUT_REASON, USELESS_ATTRIBUTE, BLANKET_CLIPPY_RESTRICTION_LINTS, SHOULD_PANIC_WITHOUT_EXPECT, @@ -546,28 +573,27 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { } fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { - if let Some(items) = &attr.meta_item_list() { - if let Some(ident) = attr.ident() { - if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { - allow_attributes::check(cx, attr); - } - if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) + if let Some(items) = &attr.meta_item_list() + && let Some(ident) = attr.ident() + { + if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { + allow_attributes::check(cx, attr); + } + if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { + allow_attributes_without_reason::check(cx, ident.name, items, attr); + } + if is_lint_level(ident.name, attr.id) { + blanket_clippy_restriction_lints::check(cx, ident.name, items); + } + if items.is_empty() || !attr.has_name(sym::deprecated) { + return; + } + for item in items { + if let MetaItemInner::MetaItem(mi) = &item + && let MetaItemKind::NameValue(lit) = &mi.kind + && mi.has_name(sym::since) { - allow_attributes_without_reason::check(cx, ident.name, items, attr); - } - if is_lint_level(ident.name, attr.id) { - blanket_clippy_restriction_lints::check(cx, ident.name, items); - } - if items.is_empty() || !attr.has_name(sym::deprecated) { - return; - } - for item in items { - if let MetaItemInner::MetaItem(mi) = &item - && let MetaItemKind::NameValue(lit) = &mi.kind - && mi.has_name(sym::since) - { - deprecated_semver::check(cx, item.span(), lit); - } + deprecated_semver::check(cx, item.span(), lit); } } } @@ -575,6 +601,22 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { if attr.has_name(sym::should_panic) { should_panic_without_expect::check(cx, attr); } + + if attr.has_name(sym::ignore) + && match &attr.kind { + AttrKind::Normal(normal_attr) => !matches!(normal_attr.item.args, AttrArgs::Eq { .. }), + AttrKind::DocComment(..) => true, + } + { + span_lint_and_help( + cx, + IGNORE_WITHOUT_REASON, + attr.span, + "`#[ignore]` without reason", + None, + "add a reason with `= \"..\"`", + ); + } } fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) { diff --git a/clippy_lints/src/attrs/repr_attributes.rs b/clippy_lints/src/attrs/repr_attributes.rs index e5cfbaf952a..df01c7fde18 100644 --- a/clippy_lints/src/attrs/repr_attributes.rs +++ b/clippy_lints/src/attrs/repr_attributes.rs @@ -30,7 +30,7 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute], diag.warn( "unqualified `#[repr(packed)]` defaults to `#[repr(Rust, packed)]`, which has no stable ABI", ) - .help("qualify the desired ABI explicity via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`") + .help("qualify the desired ABI explicitly via `#[repr(C, packed)]` or `#[repr(Rust, packed)]`") .span_label(packed_span, "`packed` representation set here"); }, ); diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 1cb43ab02a3..d75b73280e6 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -14,75 +14,75 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { if attr.span.in_external_macro(cx.sess().source_map()) { return; } - if let Some(lint_list) = &attr.meta_item_list() { - if attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id)) { - for lint in lint_list { - match item.kind { - ItemKind::Use(..) => { - let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else { - return; - }; + if let Some(lint_list) = &attr.meta_item_list() + && attr.ident().is_some_and(|ident| is_lint_level(ident.name, attr.id)) + { + for lint in lint_list { + match item.kind { + ItemKind::Use(..) => { + let (namespace @ (Some(sym::clippy) | None), Some(name)) = namespace_and_lint(lint) else { + return; + }; - if namespace.is_none() - && matches!( - name.as_str(), - "ambiguous_glob_reexports" - | "dead_code" - | "deprecated" - | "hidden_glob_reexports" - | "unreachable_pub" - | "unused" - | "unused_braces" - | "unused_import_braces" - | "unused_imports" - ) - { - return; - } + if namespace.is_none() + && matches!( + name.as_str(), + "ambiguous_glob_reexports" + | "dead_code" + | "deprecated" + | "hidden_glob_reexports" + | "unreachable_pub" + | "unused" + | "unused_braces" + | "unused_import_braces" + | "unused_imports" + ) + { + return; + } - if namespace == Some(sym::clippy) - && matches!( - name.as_str(), - "wildcard_imports" - | "enum_glob_use" - | "redundant_pub_crate" - | "macro_use_imports" - | "unsafe_removed_from_name" - | "module_name_repetitions" - | "single_component_path_imports" - | "disallowed_types" - | "unused_trait_names" - ) - { - return; - } - }, - ItemKind::ExternCrate(..) => { - if is_word(lint, sym::unused_imports) && skip_unused_imports { - return; - } - if is_word(lint, sym::unused_extern_crates) { - return; - } - }, - _ => {}, - } + if namespace == Some(sym::clippy) + && matches!( + name.as_str(), + "wildcard_imports" + | "enum_glob_use" + | "redundant_pub_crate" + | "macro_use_imports" + | "unsafe_removed_from_name" + | "module_name_repetitions" + | "single_component_path_imports" + | "disallowed_types" + | "unused_trait_names" + ) + { + return; + } + }, + ItemKind::ExternCrate(..) => { + if is_word(lint, sym::unused_imports) && skip_unused_imports { + return; + } + if is_word(lint, sym::unused_extern_crates) { + return; + } + }, + _ => {}, } - let line_span = first_line_of_span(cx, attr.span); + } + let line_span = first_line_of_span(cx, attr.span); - if let Some(src) = line_span.get_source_text(cx) { - if src.contains("#[") { - #[expect(clippy::collapsible_span_lint_calls)] - span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| { - diag.span_suggestion( - line_span, - "if you just forgot a `!`, use", - src.replacen("#[", "#![", 1), - Applicability::MaybeIncorrect, - ); - }); - } - } + if let Some(src) = line_span.get_source_text(cx) + && src.contains("#[") + { + #[expect(clippy::collapsible_span_lint_calls)] + span_lint_and_then(cx, USELESS_ATTRIBUTE, line_span, "useless lint attribute", |diag| { + diag.span_suggestion( + line_span, + "if you just forgot a `!`, use", + src.replacen("#[", "#![", 1), + Applicability::MaybeIncorrect, + ); + }); } } } diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 92a0c7f9acb..52d1d5b4c67 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -179,9 +179,14 @@ pub struct AwaitHolding { impl AwaitHolding { pub(crate) fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { - Self { - def_ids: create_disallowed_map(tcx, &conf.await_holding_invalid_types), - } + let (def_ids, _) = create_disallowed_map( + tcx, + &conf.await_holding_invalid_types, + crate::disallowed_types::def_kind_predicate, + "type", + false, + ); + Self { def_ids } } } @@ -192,10 +197,9 @@ impl<'tcx> LateLintPass<'tcx> for AwaitHolding { def_id, .. }) = expr.kind + && let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) { - if let Some(coroutine_layout) = cx.tcx.mir_coroutine_witnesses(*def_id) { - self.check_interior_types(cx, coroutine_layout); - } + self.check_interior_types(cx, coroutine_layout); } } } diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index 612712d1684..129e7747840 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -1,6 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::HasSession; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_else_clause, is_in_const_context}; +use clippy_utils::{higher, is_else_clause, is_in_const_context, span_contains_comment}; use rustc_ast::LitKind; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -46,18 +47,25 @@ declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let ExprKind::If(cond, then, Some(else_)) = expr.kind - && matches!(cond.kind, ExprKind::DropTemps(_)) + if !expr.span.from_expansion() + && let Some(higher::If { + cond, + then, + r#else: Some(r#else), + }) = higher::If::hir(expr) && let Some(then_lit) = as_int_bool_lit(then) - && let Some(else_lit) = as_int_bool_lit(else_) + && let Some(else_lit) = as_int_bool_lit(r#else) && then_lit != else_lit - && !expr.span.from_expansion() && !is_in_const_context(cx) { let ty = cx.typeck_results().expr_ty(then); - let mut applicability = Applicability::MachineApplicable; + let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; let snippet = { - let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability); + let mut sugg = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "..", &mut applicability); if !then_lit { sugg = !sugg; } @@ -72,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { s }; - let into_snippet = snippet.clone().maybe_par(); + let into_snippet = snippet.clone().maybe_paren(); let as_snippet = snippet.as_ty(ty); span_lint_and_then( @@ -91,10 +99,11 @@ impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { } } -fn as_int_bool_lit(e: &Expr<'_>) -> Option<bool> { - if let ExprKind::Block(b, _) = e.kind +fn as_int_bool_lit(expr: &Expr<'_>) -> Option<bool> { + if let ExprKind::Block(b, _) = expr.kind && b.stmts.is_empty() && let Some(e) = b.expr + && !e.span.from_expansion() && let ExprKind::Lit(lit) = e.kind && let LitKind::Int(x, _) = lit.node { diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 7bb5dbee126..bc6ba84772b 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -13,7 +13,7 @@ use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; +use rustc_span::{Span, SyntaxContext, sym}; declare_clippy_lint! { /// ### What it does @@ -242,11 +242,11 @@ struct Hir2Qmm<'a, 'tcx, 'v> { impl<'v> Hir2Qmm<'_, '_, 'v> { fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec<Bool>) -> Result<Vec<Bool>, String> { for a in a { - if let ExprKind::Binary(binop, lhs, rhs) = &a.kind { - if binop.node == op { - v = self.extract(op, &[lhs, rhs], v)?; - continue; - } + if let ExprKind::Binary(binop, lhs, rhs) = &a.kind + && binop.node == op + { + v = self.extract(op, &[lhs, rhs], v)?; + continue; } v.push(self.run(a)?); } @@ -349,9 +349,13 @@ impl SuggestContext<'_, '_, '_> { if let Some(str) = simplify_not(self.cx, self.msrv, terminal) { self.output.push_str(&str); } else { - self.output.push('!'); - self.output - .push_str(&Sugg::hir_opt(self.cx, terminal)?.maybe_par().to_string()); + let mut app = Applicability::MachineApplicable; + let snip = Sugg::hir_with_context(self.cx, terminal, SyntaxContext::root(), "", &mut app); + // Ignore the case If the expression is inside a macro expansion, or the default snippet is used + if app != Applicability::MachineApplicable { + return None; + } + self.output.push_str(&(!snip).to_string()); } }, True | False | Not(_) => { @@ -414,12 +418,12 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio let lhs_snippet = lhs.span.get_source_text(cx)?; let rhs_snippet = rhs.span.get_source_text(cx)?; - if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) { - if let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) { - // e.g. `(a as u64) < b`. Without the parens the `<` is - // interpreted as a start of generic arguments for `u64` - return Some(format!("({lhs_snippet}){op}{rhs_snippet}")); - } + if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) + && let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) + { + // e.g. `(a as u64) < b`. Without the parens the `<` is + // interpreted as a start of generic arguments for `u64` + return Some(format!("({lhs_snippet}){op}{rhs_snippet}")); } Some(format!("{lhs_snippet}{op}{rhs_snippet}")) diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 8892a9e6b6b..7cde007a9b6 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -2,7 +2,7 @@ use crate::reference::DEREF_ADDROF; use clippy_utils::diagnostics::span_lint_and_then; 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 clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed, is_mutable}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; @@ -73,6 +73,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { } }) && !is_from_proc_macro(cx, e) + && let e_ty = cx.typeck_results().expr_ty_adjusted(e) + // check if the reference is coercing to a mutable reference + && (!matches!(e_ty.kind(), ty::Ref(_, _, Mutability::Mut)) || is_mutable(cx, deref_target)) && let Some(deref_text) = deref_target.span.get_source_text(cx) { span_lint_and_then( @@ -90,10 +93,10 @@ impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { // has deref trait -> give 2 help // doesn't have deref trait -> give 1 help - if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() { - if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) { - return; - } + if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait() + && !implements_trait(cx, *inner_ty, deref_trait_id, &[]) + { + return; } diag.span_suggestion( diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs index 64345c81a24..ad0a4f8cdf3 100644 --- a/clippy_lints/src/casts/borrow_as_ptr.rs +++ b/clippy_lints/src/casts/borrow_as_ptr.rs @@ -1,11 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::Msrv; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::{is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core}; +use clippy_utils::{get_parent_expr, is_expr_temporary_value, is_lint_allowed, msrvs, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, AutoBorrow}; use rustc_span::BytePos; use super::BORROW_AS_PTR; @@ -29,10 +30,6 @@ pub(super) fn check<'tcx>( } let (suggestion, span) = if msrv.meets(cx, msrvs::RAW_REF_OP) { - let operator_kind = match mutability { - Mutability::Not => "const", - Mutability::Mut => "mut", - }; // Make sure that the span to be replaced doesn't include parentheses, that could break the // suggestion. let span = if has_enclosing_paren(snippet_with_applicability(cx, expr.span, "", &mut app)) { @@ -42,7 +39,7 @@ pub(super) fn check<'tcx>( } else { expr.span }; - (format!("&raw {operator_kind} {snip}"), span) + (format!("&raw {} {snip}", mutability.ptr_str()), span) } else { let Some(std_or_core) = std_or_core(cx) else { return false; @@ -59,3 +56,25 @@ pub(super) fn check<'tcx>( } false } + +/// Check for an implicit cast from reference to raw pointer outside an explicit `as`. +pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) { + if !expr.span.from_expansion() + && let ExprKind::AddrOf(BorrowKind::Ref, _, pointee) = expr.kind + && !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..))) + && let [deref, borrow] = cx.typeck_results().expr_adjustments(expr) + && matches!(deref.kind, Adjust::Deref(..)) + && let Adjust::Borrow(AutoBorrow::RawPtr(mutability)) = borrow.kind + // Do not suggest taking a raw pointer to a temporary value + && !is_expr_temporary_value(cx, pointee) + { + span_lint_and_then(cx, BORROW_AS_PTR, expr.span, "implicit borrow as raw pointer", |diag| { + diag.span_suggestion_verbose( + expr.span.until(pointee.span), + "use a raw pointer instead", + format!("&raw {} ", mutability.ptr_str()), + Applicability::MachineApplicable, + ); + }); + } +} diff --git a/clippy_lints/src/casts/cast_abs_to_unsigned.rs b/clippy_lints/src/casts/cast_abs_to_unsigned.rs index 8b3529e84fc..164d3540253 100644 --- a/clippy_lints/src/casts/cast_abs_to_unsigned.rs +++ b/clippy_lints/src/casts/cast_abs_to_unsigned.rs @@ -36,7 +36,7 @@ pub(super) fn check( span, format!("casting the result of `{cast_from}::abs()` to {cast_to}"), "replace with", - format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_par()), + format!("{}.unsigned_abs()", Sugg::hir(cx, receiver, "..").maybe_paren()), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 3ae43732dc0..0f066fae118 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -42,7 +42,7 @@ pub(super) fn check( diag.span_suggestion_verbose( expr.span, "use `Into::into` instead", - format!("{}.into()", from_sugg.maybe_par()), + format!("{}.into()", from_sugg.maybe_paren()), applicability, ); }, diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index ca973f4bb1a..8742f5f1a0e 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -64,11 +64,11 @@ fn apply_reductions(cx: &LateContext<'_>, nbits: u64, expr: &Expr<'_>, signed: b apply_reductions(cx, nbits, left, signed).min(max_bits.unwrap_or(u64::MAX)) }, ExprKind::MethodCall(method, _, [lo, hi], _) => { - if method.ident.as_str() == "clamp" { + if method.ident.as_str() == "clamp" //FIXME: make this a diagnostic item - if let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) { - return lo_bits.max(hi_bits); - } + && let (Some(lo_bits), Some(hi_bits)) = (get_constant_bits(cx, lo), get_constant_bits(cx, hi)) + { + return lo_bits.max(hi_bits); } nbits }, @@ -185,7 +185,7 @@ fn offer_suggestion( ) { let cast_to_snip = snippet(cx, cast_to_span, ".."); let suggestion = if cast_to_snip == "_" { - format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par()) + format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_paren()) } else { format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, "..")) }; diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 57a135abc2e..3fca0f89707 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -19,16 +19,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { cx.typeck_results().expr_ty(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.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. - && !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty()) - { - let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr)); - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } + } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind + && 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. + && !is_hir_ty_cfg_dependant(cx, cast_to.as_unambig_ty()) + { + let (cast_from, cast_to) = (cx.typeck_results().expr_ty(self_arg), cx.typeck_results().expr_ty(expr)); + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); } } diff --git a/clippy_lints/src/casts/cast_slice_different_sizes.rs b/clippy_lints/src/casts/cast_slice_different_sizes.rs index c48f253606d..a5b295c88b1 100644 --- a/clippy_lints/src/casts/cast_slice_different_sizes.rs +++ b/clippy_lints/src/casts/cast_slice_different_sizes.rs @@ -21,42 +21,41 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) start_ty, end_ty, }) = expr_cast_chain_tys(cx, expr) + && let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) { - if let (Ok(from_layout), Ok(to_layout)) = (cx.layout_of(start_ty.ty), cx.layout_of(end_ty.ty)) { - let from_size = from_layout.size.bytes(); - let to_size = to_layout.size.bytes(); - if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { - span_lint_and_then( - cx, - CAST_SLICE_DIFFERENT_SIZES, - expr.span, - format!( - "casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count", - start_ty.ty, end_ty.ty, - ), - |diag| { - let ptr_snippet = source::snippet(cx, left_cast.span, ".."); + let from_size = from_layout.size.bytes(); + let to_size = to_layout.size.bytes(); + if from_size != to_size && from_size != 0 && to_size != 0 && msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { + span_lint_and_then( + cx, + CAST_SLICE_DIFFERENT_SIZES, + expr.span, + format!( + "casting between raw pointers to `[{}]` (element size {from_size}) and `[{}]` (element size {to_size}) does not adjust the count", + start_ty.ty, end_ty.ty, + ), + |diag| { + let ptr_snippet = source::snippet(cx, left_cast.span, ".."); - let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl { - Mutability::Mut => ("_mut", "mut"), - Mutability::Not => ("", "const"), - }; - let sugg = format!( - "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)", - // get just the ty from the TypeAndMut so that the printed type isn't something like `mut - // T`, extract just the `T` - end_ty.ty - ); + let (mutbl_fn_str, mutbl_ptr_str) = match end_ty.mutbl { + Mutability::Mut => ("_mut", "mut"), + Mutability::Not => ("", "const"), + }; + let sugg = format!( + "core::ptr::slice_from_raw_parts{mutbl_fn_str}({ptr_snippet} as *{mutbl_ptr_str} {}, ..)", + // get just the ty from the TypeAndMut so that the printed type isn't something like `mut + // T`, extract just the `T` + end_ty.ty + ); - diag.span_suggestion( - expr.span, - format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"), - sugg, - rustc_errors::Applicability::HasPlaceholders, - ); - }, - ); - } + diag.span_suggestion( + expr.span, + format!("replace with `ptr::slice_from_raw_parts{mutbl_fn_str}`"), + sugg, + rustc_errors::Applicability::HasPlaceholders, + ); + }, + ); } } } diff --git a/clippy_lints/src/casts/manual_dangling_ptr.rs b/clippy_lints/src/casts/manual_dangling_ptr.rs new file mode 100644 index 00000000000..8ace27eca89 --- /dev/null +++ b/clippy_lints/src/casts/manual_dangling_ptr.rs @@ -0,0 +1,82 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::ty::is_normalizable; +use clippy_utils::{expr_or_init, match_def_path, path_def_id, paths, std_or_core}; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, GenericArg, Mutability, QPath, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_span::source_map::Spanned; + +use super::MANUAL_DANGLING_PTR; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, from: &Expr<'_>, to: &Ty<'_>) { + if let TyKind::Ptr(ref ptr_ty) = to.kind { + let init_expr = expr_or_init(cx, from); + if is_expr_const_aligned(cx, init_expr, ptr_ty.ty) + && let Some(std_or_core) = std_or_core(cx) + { + let sugg_fn = match ptr_ty.mutbl { + Mutability::Not => "ptr::dangling", + Mutability::Mut => "ptr::dangling_mut", + }; + + let sugg = if let TyKind::Infer(()) = ptr_ty.ty.kind { + format!("{std_or_core}::{sugg_fn}()") + } else if let Some(mut_ty_snip) = ptr_ty.ty.span.get_source_text(cx) { + format!("{std_or_core}::{sugg_fn}::<{mut_ty_snip}>()") + } else { + return; + }; + + span_lint_and_sugg( + cx, + MANUAL_DANGLING_PTR, + expr.span, + "manual creation of a dangling pointer", + "use", + sugg, + Applicability::MachineApplicable, + ); + } + } +} + +// Checks if the given expression is a call to `align_of` whose generic argument matches the target +// type, or a positive constant literal that matches the target type's alignment. +fn is_expr_const_aligned(cx: &LateContext<'_>, expr: &Expr<'_>, to: &Ty<'_>) -> bool { + match expr.kind { + ExprKind::Call(fun, _) => is_align_of_call(cx, fun, to), + ExprKind::Lit(lit) => is_literal_aligned(cx, lit, to), + _ => false, + } +} + +fn is_align_of_call(cx: &LateContext<'_>, fun: &Expr<'_>, to: &Ty<'_>) -> bool { + if let ExprKind::Path(QPath::Resolved(_, path)) = fun.kind + && let Some(fun_id) = path_def_id(cx, fun) + && match_def_path(cx, fun_id, &paths::ALIGN_OF) + && let Some(args) = path.segments.last().and_then(|seg| seg.args) + && let [GenericArg::Type(generic_ty)] = args.args + { + let typeck = cx.typeck_results(); + return typeck.node_type(generic_ty.hir_id) == typeck.node_type(to.hir_id); + } + false +} + +fn is_literal_aligned(cx: &LateContext<'_>, lit: &Spanned<LitKind>, to: &Ty<'_>) -> bool { + let LitKind::Int(val, _) = lit.node else { return false }; + if val == 0 { + return false; + } + let to_mid_ty = cx.typeck_results().node_type(to.hir_id); + is_normalizable(cx, cx.param_env, to_mid_ty) + && cx + .tcx + .layout_of(cx.typing_env().as_query_input(to_mid_ty)) + .is_ok_and(|layout| { + let align = u128::from(layout.align.abi.bytes()); + u128::from(val) <= align + }) +} diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index dc2a1fa85bf..76931fce209 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -17,6 +17,7 @@ mod char_lit_as_u8; mod fn_to_numeric_cast; mod fn_to_numeric_cast_any; mod fn_to_numeric_cast_with_truncation; +mod manual_dangling_ptr; mod ptr_as_ptr; mod ptr_cast_constness; mod ref_as_ptr; @@ -71,7 +72,7 @@ declare_clippy_lint! { /// ### Example /// ```no_run /// let y: i8 = -1; - /// y as u128; // will return 18446744073709551615 + /// y as u64; // will return 18446744073709551615 /// ``` #[clippy::version = "pre 1.29.0"] pub CAST_SIGN_LOSS, @@ -759,6 +760,32 @@ declare_clippy_lint! { "detects `as *mut _` and `as *const _` conversion" } +declare_clippy_lint! { + /// ### What it does + /// Checks for casts of small constant literals or `mem::align_of` results to raw pointers. + /// + /// ### Why is this bad? + /// This creates a dangling pointer and is better expressed as + /// {`std`, `core`}`::ptr::`{`dangling`, `dangling_mut`}. + /// + /// ### Example + /// ```no_run + /// let ptr = 4 as *const u32; + /// let aligned = std::mem::align_of::<u32>() as *const u32; + /// let mut_ptr: *mut i64 = 8 as *mut _; + /// ``` + /// Use instead: + /// ```no_run + /// let ptr = std::ptr::dangling::<u32>(); + /// let aligned = std::ptr::dangling::<u32>(); + /// let mut_ptr: *mut i64 = std::ptr::dangling_mut(); + /// ``` + #[clippy::version = "1.87.0"] + pub MANUAL_DANGLING_PTR, + style, + "casting small constant literals to pointers to create dangling pointers" +} + pub struct Casts { msrv: Msrv, } @@ -795,6 +822,7 @@ impl_lint_pass!(Casts => [ ZERO_PTR, REF_AS_PTR, AS_POINTER_UNDERSCORE, + MANUAL_DANGLING_PTR, ]); impl<'tcx> LateLintPass<'tcx> for Casts { @@ -823,6 +851,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts { fn_to_numeric_cast_with_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to); zero_ptr::check(cx, expr, cast_from_expr, cast_to_hir); + if self.msrv.meets(cx, msrvs::MANUAL_DANGLING_PTR) { + manual_dangling_ptr::check(cx, expr, cast_from_expr, cast_to_hir); + } + if cast_to.is_numeric() { cast_possible_truncation::check(cx, expr, cast_from_expr, cast_from, cast_to, cast_to_hir.span); if cast_from.is_numeric() { @@ -846,6 +878,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } } + if self.msrv.meets(cx, msrvs::RAW_REF_OP) { + borrow_as_ptr::check_implicit_cast(cx, expr); + } cast_ptr_alignment::check(cx, expr); char_lit_as_u8::check(cx, expr); ptr_as_ptr::check(cx, expr, self.msrv); diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index d57e391b55d..6f944914b8f 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -81,7 +81,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: Msrv) { ( "try `pointer::cast`, a safer alternative", - format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_par()), + format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_paren()), ) }; diff --git a/clippy_lints/src/casts/ptr_cast_constness.rs b/clippy_lints/src/casts/ptr_cast_constness.rs index cad9c1df273..2471c735551 100644 --- a/clippy_lints/src/casts/ptr_cast_constness.rs +++ b/clippy_lints/src/casts/ptr_cast_constness.rs @@ -53,7 +53,8 @@ pub(super) fn check<'tcx>( } if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) { - let sugg = Sugg::hir(cx, cast_expr, "_"); + let mut app = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); let constness = match *to_mutbl { Mutability::Not => "const", Mutability::Mut => "mut", @@ -65,8 +66,8 @@ pub(super) fn check<'tcx>( expr.span, "`as` casting between raw pointers while changing only its constness", format!("try `pointer::cast_{constness}`, a safer alternative"), - format!("{}.cast_{constness}()", sugg.maybe_par()), - Applicability::MachineApplicable, + format!("{}.cast_{constness}()", sugg.maybe_paren()), + app, ); } } diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 72953818a70..ae994e94a32 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -130,11 +130,11 @@ pub(super) fn check<'tcx>( | LitKind::Float(_, LitFloatType::Suffixed(_)) if cast_from.kind() == cast_to.kind() => { - if let Some(src) = cast_expr.span.get_source_text(cx) { - if let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) { - lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to); - return true; - } + if let Some(src) = cast_expr.span.get_source_text(cx) + && let Some(num_lit) = NumericLiteral::from_lit_kind(&src, &lit.node) + { + lint_unnecessary_cast(cx, expr, num_lit.integer, cast_from, cast_to); + return true; } }, _ => {}, diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index b36c8662289..8ada608049c 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -253,11 +253,11 @@ fn get_types_from_cast<'a>( match limit.kind { // `from_type::from(_)` ExprKind::Call(path, _) => { - if let ExprKind::Path(ref path) = path.kind { + if let ExprKind::Path(ref path) = path.kind // `to_type` - if let Some(to_type) = get_implementing_type(path, types, func) { - return Some((from_type, to_type)); - } + && let Some(to_type) = get_implementing_type(path, types, func) + { + return Some((from_type, to_type)); } }, // `to_type::MAX` diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index a1ff20dee72..1d44c7e9c88 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -62,6 +62,7 @@ impl CognitiveComplexity { let mut cc = 1u64; let mut returns = 0u64; + let mut prev_expr: Option<&ExprKind<'tcx>> = None; let _: Option<!> = for_each_expr_without_closures(expr, |e| { match e.kind { ExprKind::If(_, _, _) => { @@ -73,9 +74,14 @@ impl CognitiveComplexity { } cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64; }, - ExprKind::Ret(_) => returns += 1, + ExprKind::Ret(_) => { + if !matches!(prev_expr, Some(ExprKind::Ret(_))) { + returns += 1; + } + }, _ => {}, } + prev_expr = Some(&e.kind); ControlFlow::Continue(()) }); diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index e73bfc6ebf7..20fae8a6775 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -1,10 +1,12 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_block, snippet_block_with_applicability}; -use clippy_utils::sugg::Sugg; -use rustc_ast::ast; +use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block, snippet_block_with_applicability}; +use rustc_ast::BinOpKind; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; -use rustc_session::declare_lint_pass; +use rustc_hir::{Block, Expr, ExprKind, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyCtxt; +use rustc_session::impl_lint_pass; use rustc_span::Span; declare_clippy_lint! { @@ -75,105 +77,152 @@ declare_clippy_lint! { "nested `else`-`if` expressions that can be collapsed (e.g., `else { if x { ... } }`)" } -declare_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); +pub struct CollapsibleIf { + let_chains_enabled: bool, + lint_commented_code: bool, +} + +impl CollapsibleIf { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + Self { + let_chains_enabled: tcx.features().let_chains(), + lint_commented_code: conf.lint_commented_code, + } + } -impl EarlyLintPass for CollapsibleIf { - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let ast::ExprKind::If(cond, then, else_) = &expr.kind + fn check_collapsible_else_if(cx: &LateContext<'_>, then_span: Span, else_block: &Block<'_>) { + if !block_starts_with_comment(cx, else_block) + && let Some(else_) = expr_block(else_block) + && cx.tcx.hir_attrs(else_.hir_id).is_empty() + && !else_.span.from_expansion() + && let ExprKind::If(..) = else_.kind + { + // Prevent "elseif" + // Check that the "else" is followed by whitespace + let up_to_else = then_span.between(else_block.span); + let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { + !c.is_whitespace() + } else { + false + }; + + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + COLLAPSIBLE_ELSE_IF, + else_block.span, + "this `else { if .. }` block can be collapsed", + "collapse nested if block", + format!( + "{}{}", + if requires_space { " " } else { "" }, + snippet_block_with_applicability(cx, else_.span, "..", Some(else_block.span), &mut applicability) + ), + applicability, + ); + } + } + + fn check_collapsible_if_if(&self, cx: &LateContext<'_>, expr: &Expr<'_>, check: &Expr<'_>, then: &Block<'_>) { + if let Some(inner) = expr_block(then) + && cx.tcx.hir_attrs(inner.hir_id).is_empty() + && let ExprKind::If(check_inner, _, None) = &inner.kind + && self.eligible_condition(check_inner) + && let ctxt = expr.span.ctxt() + && inner.span.ctxt() == ctxt + && (self.lint_commented_code || !block_starts_with_comment(cx, then)) + { + span_lint_and_then( + cx, + COLLAPSIBLE_IF, + expr.span, + "this `if` statement can be collapsed", + |diag| { + let then_open_bracket = then.span.split_at(1).0.with_leading_whitespace(cx).into_span(); + let then_closing_bracket = { + let end = then.span.shrink_to_hi(); + end.with_lo(end.lo() - rustc_span::BytePos(1)) + .with_leading_whitespace(cx) + .into_span() + }; + let inner_if = inner.span.split_at(2).0; + let mut sugg = vec![ + // Remove the outer then block `{` + (then_open_bracket, String::new()), + // Remove the outer then block '}' + (then_closing_bracket, String::new()), + // Replace inner `if` by `&&` + (inner_if, String::from("&&")), + ]; + sugg.extend(parens_around(check)); + sugg.extend(parens_around(check_inner)); + + diag.multipart_suggestion("collapse nested if block", sugg, Applicability::MachineApplicable); + }, + ); + } + } + + pub fn eligible_condition(&self, cond: &Expr<'_>) -> bool { + self.let_chains_enabled || !matches!(cond.kind, ExprKind::Let(..)) + } +} + +impl_lint_pass!(CollapsibleIf => [COLLAPSIBLE_IF, COLLAPSIBLE_ELSE_IF]); + +impl LateLintPass<'_> for CollapsibleIf { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::If(cond, then, else_) = &expr.kind && !expr.span.from_expansion() { - if let Some(else_) = else_ { - check_collapsible_maybe_if_let(cx, then.span, else_); - } else if !matches!(cond.kind, ast::ExprKind::Let(..)) { - check_collapsible_no_if_let(cx, expr, cond, then); + if let Some(else_) = else_ + && let ExprKind::Block(else_, None) = else_.kind + { + Self::check_collapsible_else_if(cx, then.span, else_); + } else if else_.is_none() + && self.eligible_condition(cond) + && let ExprKind::Block(then, None) = then.kind + { + self.check_collapsible_if_if(cx, expr, cond, then); } } } } -fn block_starts_with_comment(cx: &EarlyContext<'_>, expr: &ast::Block) -> bool { +fn block_starts_with_comment(cx: &LateContext<'_>, block: &Block<'_>) -> bool { // We trim all opening braces and whitespaces and then check if the next string is a comment. - let trimmed_block_text = snippet_block(cx, expr.span, "..", None) + let trimmed_block_text = snippet_block(cx, block.span, "..", None) .trim_start_matches(|c: char| c.is_whitespace() || c == '{') .to_owned(); trimmed_block_text.starts_with("//") || trimmed_block_text.starts_with("/*") } -fn check_collapsible_maybe_if_let(cx: &EarlyContext<'_>, then_span: Span, else_: &ast::Expr) { - if let ast::ExprKind::Block(ref block, _) = else_.kind - && !block_starts_with_comment(cx, block) - && let Some(else_) = expr_block(block) - && else_.attrs.is_empty() - && !else_.span.from_expansion() - && let ast::ExprKind::If(..) = else_.kind - { - // Prevent "elseif" - // Check that the "else" is followed by whitespace - let up_to_else = then_span.between(block.span); - let requires_space = if let Some(c) = snippet(cx, up_to_else, "..").chars().last() { - !c.is_whitespace() - } else { - false - }; - - let mut applicability = Applicability::MachineApplicable; - span_lint_and_sugg( - cx, - COLLAPSIBLE_ELSE_IF, - block.span, - "this `else { if .. }` block can be collapsed", - "collapse nested if block", - format!( - "{}{}", - if requires_space { " " } else { "" }, - snippet_block_with_applicability(cx, else_.span, "..", Some(block.span), &mut applicability) - ), - applicability, - ); - } -} - -fn check_collapsible_no_if_let(cx: &EarlyContext<'_>, expr: &ast::Expr, check: &ast::Expr, then: &ast::Block) { - if !block_starts_with_comment(cx, then) - && let Some(inner) = expr_block(then) - && inner.attrs.is_empty() - && let ast::ExprKind::If(ref check_inner, ref content, None) = inner.kind - // Prevent triggering on `if c { if let a = b { .. } }`. - && !matches!(check_inner.kind, ast::ExprKind::Let(..)) - && let ctxt = expr.span.ctxt() - && inner.span.ctxt() == ctxt - { - span_lint_and_then( - cx, - COLLAPSIBLE_IF, - expr.span, - "this `if` statement can be collapsed", - |diag| { - let mut app = Applicability::MachineApplicable; - let lhs = Sugg::ast(cx, check, "..", ctxt, &mut app); - let rhs = Sugg::ast(cx, check_inner, "..", ctxt, &mut app); - diag.span_suggestion( - expr.span, - "collapse nested if block", - format!( - "if {} {}", - lhs.and(&rhs), - snippet_block(cx, content.span, "..", Some(expr.span)), - ), - app, // snippet - ); - }, - ); +/// If `block` is a block with either one expression or a statement containing an expression, +/// return the expression. We don't peel blocks recursively, as extra blocks might be intentional. +fn expr_block<'tcx>(block: &Block<'tcx>) -> Option<&'tcx Expr<'tcx>> { + match block.stmts { + [] => block.expr, + [stmt] => { + if let StmtKind::Semi(expr) = stmt.kind { + Some(expr) + } else { + None + } + }, + _ => None, } } -/// If the block contains only one expression, return it. -fn expr_block(block: &ast::Block) -> Option<&ast::Expr> { - if let [stmt] = &*block.stmts - && let ast::StmtKind::Expr(expr) | ast::StmtKind::Semi(expr) = &stmt.kind +/// If the expression is a `||`, suggest parentheses around it. +fn parens_around(expr: &Expr<'_>) -> Vec<(Span, String)> { + if let ExprKind::Binary(op, _, _) = expr.peel_drop_temps().kind + && op.node == BinOpKind::Or { - Some(expr) + vec![ + (expr.span.shrink_to_lo(), String::from("(")), + (expr.span.shrink_to_hi(), String::from(")")), + ] } else { - None + vec![] } } diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 0e7f01e44b0..9c3009a86cd 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -125,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { let ExprKind::Binary(_, lhs, rhs) = conds[0].kind else { unreachable!(); }; - let lhs = Sugg::hir(cx, lhs, "..").maybe_par(); + let lhs = Sugg::hir(cx, lhs, "..").maybe_paren(); let rhs = Sugg::hir(cx, rhs, "..").addr(); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 03ed9c657b3..42fbe6438d4 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -256,7 +256,7 @@ fn lint_branches_sharing_code<'tcx>( let suggestion = reindent_multiline(&suggestion, true, indent); let span = span.with_hi(last_block.span.hi()); - // Improve formatting if the inner block has indention (i.e. normal Rust formatting) + // Improve formatting if the inner block has indentation (i.e. normal Rust formatting) let span = span .map_range(cx, |src, range| { (range.start > 4 && src.get(range.start - 4..range.start)? == " ") @@ -539,10 +539,10 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo .filter(|stmt| !ignore_span.overlaps(stmt.span)) .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); - if let Some(expr) = block.expr { - if res.is_continue() { - res = intravisit::walk_expr(&mut walker, expr); - } + if let Some(expr) = block.expr + && res.is_continue() + { + res = intravisit::walk_expr(&mut walker, expr); } res.is_break() diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs index 4d908af4084..9f82f876727 100644 --- a/clippy_lints/src/declare_clippy_lint.rs +++ b/clippy_lints/src/declare_clippy_lint.rs @@ -165,17 +165,4 @@ macro_rules! declare_clippy_lint { $(, $eval_always)? } }; - - ( - $(#[doc = $lit:literal])* - pub $lint_name:ident, - internal, - $desc:literal - ) => { - declare_clippy_lint! {@ - $(#[doc = $lit])* - pub $lint_name, Allow, crate::LintCategory::Internal, $desc, - None, "0.0.0" - } - }; } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 39e45163707..2cccd6ba270 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -3,36 +3,6 @@ // Manual edits will be overwritten. pub static LINTS: &[&crate::LintInfo] = &[ - #[cfg(feature = "internal")] - crate::utils::internal_lints::almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::invalid_paths::INVALID_PATHS_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::DEFAULT_LINT_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::LINT_WITHOUT_LINT_PASS_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::lint_without_lint_pass::MISSING_CLIPPY_VERSION_ATTRIBUTE_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::msrv_attr_impl::MISSING_MSRV_ATTR_IMPL_INFO, - #[cfg(feature = "internal")] - crate::utils::internal_lints::outer_expn_data_pass::OUTER_EXPN_EXPN_DATA_INFO, - #[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, @@ -52,6 +22,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::attrs::DEPRECATED_CLIPPY_CFG_ATTR_INFO, crate::attrs::DEPRECATED_SEMVER_INFO, crate::attrs::DUPLICATED_ATTRIBUTES_INFO, + crate::attrs::IGNORE_WITHOUT_REASON_INFO, crate::attrs::INLINE_ALWAYS_INFO, crate::attrs::MIXED_ATTRIBUTES_STYLE_INFO, crate::attrs::NON_MINIMAL_CFG_INFO, @@ -96,6 +67,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::casts::FN_TO_NUMERIC_CAST_INFO, crate::casts::FN_TO_NUMERIC_CAST_ANY_INFO, crate::casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION_INFO, + crate::casts::MANUAL_DANGLING_PTR_INFO, crate::casts::PTR_AS_PTR_INFO, crate::casts::PTR_CAST_CONSTNESS_INFO, crate::casts::REF_AS_PTR_INFO, @@ -286,6 +258,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::literal_representation::UNREADABLE_LITERAL_INFO, crate::literal_representation::UNUSUAL_BYTE_GROUPINGS_INFO, crate::literal_string_with_formatting_args::LITERAL_STRING_WITH_FORMATTING_ARGS_INFO, + crate::loops::CHAR_INDICES_AS_BYTE_INDICES_INFO, crate::loops::EMPTY_LOOP_INFO, crate::loops::EXPLICIT_COUNTER_LOOP_INFO, crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO, @@ -312,6 +285,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO, crate::macro_use::MACRO_USE_IMPORTS_INFO, crate::main_recursion::MAIN_RECURSION_INFO, + crate::manual_abs_diff::MANUAL_ABS_DIFF_INFO, crate::manual_assert::MANUAL_ASSERT_INFO, crate::manual_async_fn::MANUAL_ASYNC_FN_INFO, crate::manual_bits::MANUAL_BITS_INFO, @@ -334,7 +308,6 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_strip::MANUAL_STRIP_INFO, - crate::manual_unwrap_or_default::MANUAL_UNWRAP_OR_DEFAULT_INFO, crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO, crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO, crate::match_result_ok::MATCH_RESULT_OK_INFO, @@ -344,10 +317,10 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::matches::MANUAL_MAP_INFO, crate::matches::MANUAL_OK_ERR_INFO, crate::matches::MANUAL_UNWRAP_OR_INFO, + crate::matches::MANUAL_UNWRAP_OR_DEFAULT_INFO, crate::matches::MATCH_AS_REF_INFO, crate::matches::MATCH_BOOL_INFO, crate::matches::MATCH_LIKE_MATCHES_MACRO_INFO, - crate::matches::MATCH_ON_VEC_ITEMS_INFO, crate::matches::MATCH_OVERLAPPING_ARM_INFO, crate::matches::MATCH_REF_PATS_INFO, crate::matches::MATCH_SAME_ARMS_INFO, @@ -488,6 +461,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::SUSPICIOUS_OPEN_OPTIONS_INFO, crate::methods::SUSPICIOUS_SPLITN_INFO, crate::methods::SUSPICIOUS_TO_OWNED_INFO, + crate::methods::SWAP_WITH_TEMPORARY_INFO, crate::methods::TYPE_ID_ON_BOX_INFO, crate::methods::UNBUFFERED_BYTES_INFO, crate::methods::UNINIT_ASSUMED_INIT_INFO, @@ -664,6 +638,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::redundant_slicing::DEREF_BY_SLICING_INFO, crate::redundant_slicing::REDUNDANT_SLICING_INFO, crate::redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES_INFO, + crate::redundant_test_prefix::REDUNDANT_TEST_PREFIX_INFO, crate::redundant_type_annotations::REDUNDANT_TYPE_ANNOTATIONS_INFO, crate::ref_option_ref::REF_OPTION_REF_INFO, crate::ref_patterns::REF_PATTERNS_INFO, diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index bbd5dc15542..f8a9037fc80 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_ty_alias; +use clippy_utils::source::SpanRangeExt as _; use hir::ExprKind; use hir::def::Res; use rustc_errors::Applicability; @@ -70,15 +71,26 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs { && let var @ ty::VariantDef { ctor: Some((hir::def::CtorKind::Const, _)), .. } = def.non_enum_variant() && !var.is_field_list_non_exhaustive() && !expr.span.from_expansion() && !qpath.span().from_expansion() + // do not suggest replacing an expression by a type name with placeholders + && !base.is_suggestable_infer_ty() { - span_lint_and_sugg( + let mut removals = vec![(expr.span.with_lo(qpath.qself_span().hi()), String::new())]; + if expr.span.with_source_text(cx, |s| s.starts_with('<')) == Some(true) { + // Remove `<`, '>` has already been removed by the existing removal expression. + removals.push((expr.span.with_hi(qpath.qself_span().lo()), String::new())); + } + span_lint_and_then( cx, DEFAULT_CONSTRUCTED_UNIT_STRUCTS, - expr.span.with_lo(qpath.qself_span().hi()), + expr.span, "use of `default` to create a unit struct", - "remove this call to `default`", - String::new(), - Applicability::MachineApplicable, + |diag| { + diag.multipart_suggestion( + "remove this call to `default`", + removals, + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index de66ead4f42..b60c11d79d4 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -42,6 +42,8 @@ declare_with_version! { DEPRECATED(DEPRECATED_VERSION): &[(&str, &str)] = &[ ("clippy::wrong_pub_self_convention", "`clippy::wrong_self_convention` now covers this case via the `avoid-breaking-exported-api` config"), #[clippy::version = "1.86.0"] ("clippy::option_map_or_err_ok", "`clippy::manual_ok_or` covers this case"), + #[clippy::version = "1.86.0"] + ("clippy::match_on_vec_items", "`clippy::indexing_slicing` covers indexing and slicing on `Vec<_>`"), // end deprecated lints. used by `cargo dev deprecate_lint` ]} diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 849c60b89b9..7da5a530eaa 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1133,61 +1133,60 @@ fn report<'tcx>( impl<'tcx> Dereferencing<'tcx> { fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) { - if let Some(outer_pat) = self.ref_locals.get_mut(&local) { - if let Some(pat) = outer_pat { - // Check for auto-deref - if !matches!( - cx.typeck_results().expr_adjustments(e), - [ - Adjustment { - kind: Adjust::Deref(_), - .. - }, - Adjustment { - kind: Adjust::Deref(_), - .. - }, + if let Some(outer_pat) = self.ref_locals.get_mut(&local) + && let Some(pat) = outer_pat + // Check for auto-deref + && !matches!( + cx.typeck_results().expr_adjustments(e), + [ + Adjustment { + kind: Adjust::Deref(_), .. - ] - ) { - match get_parent_expr(cx, e) { - // Field accesses are the same no matter the number of references. - Some(Expr { - kind: ExprKind::Field(..), - .. - }) => (), - Some(&Expr { - span, - kind: ExprKind::Unary(UnOp::Deref, _), - .. - }) if !span.from_expansion() => { - // Remove explicit deref. - let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0; - pat.replacements.push((span, snip.into())); - }, - Some(parent) if !parent.span.from_expansion() => { - // Double reference might be needed at this point. - if parent.precedence() == ExprPrecedence::Unambiguous { - // Parentheses would be needed here, don't lint. - *outer_pat = None; - } else { - pat.always_deref = false; - let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0; - pat.replacements.push((e.span, format!("&{snip}"))); - } - }, - _ if !e.span.from_expansion() => { - // Double reference might be needed at this point. - pat.always_deref = false; - let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app); - pat.replacements.push((e.span, format!("&{snip}"))); - }, - // Edge case for macros. The span of the identifier will usually match the context of the - // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc - // macros - _ => *outer_pat = None, + }, + Adjustment { + kind: Adjust::Deref(_), + .. + }, + .. + ] + ) + { + match get_parent_expr(cx, e) { + // Field accesses are the same no matter the number of references. + Some(Expr { + kind: ExprKind::Field(..), + .. + }) => (), + Some(&Expr { + span, + kind: ExprKind::Unary(UnOp::Deref, _), + .. + }) if !span.from_expansion() => { + // Remove explicit deref. + let snip = snippet_with_context(cx, e.span, span.ctxt(), "..", &mut pat.app).0; + pat.replacements.push((span, snip.into())); + }, + Some(parent) if !parent.span.from_expansion() => { + // Double reference might be needed at this point. + if parent.precedence() == ExprPrecedence::Unambiguous { + // Parentheses would be needed here, don't lint. + *outer_pat = None; + } else { + pat.always_deref = false; + let snip = snippet_with_context(cx, e.span, parent.span.ctxt(), "..", &mut pat.app).0; + pat.replacements.push((e.span, format!("&{snip}"))); } - } + }, + _ if !e.span.from_expansion() => { + // Double reference might be needed at this point. + pat.always_deref = false; + let snip = snippet_with_applicability(cx, e.span, "..", &mut pat.app); + pat.replacements.push((e.span, format!("&{snip}"))); + }, + // Edge case for macros. The span of the identifier will usually match the context of the + // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc + // macros + _ => *outer_pat = None, } } } diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 8d9222e4bf6..10331b3855b 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -94,18 +94,18 @@ fn check_struct<'tcx>( ty_args: GenericArgsRef<'_>, typeck_results: &'tcx TypeckResults<'tcx>, ) { - if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind { - if let Some(PathSegment { args, .. }) = p.segments.last() { - let args = args.map(|a| a.args).unwrap_or(&[]); - - // ty_args contains the generic parameters of the type declaration, while args contains the - // arguments used at instantiation time. If both len are not equal, it means that some - // parameters were not provided (which means that the default values were used); in this - // case we will not risk suggesting too broad a rewrite. We won't either if any argument - // is a type or a const. - if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) { - return; - } + if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind + && let Some(PathSegment { args, .. }) = p.segments.last() + { + let args = args.map(|a| a.args).unwrap_or(&[]); + + // ty_args contains the generic parameters of the type declaration, while args contains the + // arguments used at instantiation time. If both len are not equal, it means that some + // parameters were not provided (which means that the default values were used); in this + // case we will not risk suggesting too broad a rewrite. We won't either if any argument + // is a type or a const. + if ty_args.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) { + return; } } @@ -188,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { self_ty, .. }) = item.kind - && !cx.tcx.has_attr(item.owner_id, sym::automatically_derived) + && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) && !item.span.from_expansion() && let Some(def_id) = trait_ref.trait_def_id() && cx.tcx.is_diagnostic_item(sym::Default, def_id) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index c85aca8d04e..06528f875a2 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive { }) = item.kind { let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived); + let is_automatically_derived = cx.tcx.is_automatically_derived(item.owner_id.to_def_id()); check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived); check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived); @@ -235,7 +235,7 @@ fn check_hash_peq<'tcx>( { // Look for the PartialEq implementations for `ty` cx.tcx.for_each_relevant_impl(peq_trait_def_id, ty, |impl_id| { - let peq_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived); + let peq_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); if !hash_is_automatically_derived || peq_is_automatically_derived { return; @@ -278,7 +278,7 @@ fn check_ord_partial_ord<'tcx>( { // Look for the PartialOrd implementations for `ty` cx.tcx.for_each_relevant_impl(partial_ord_trait_def_id, ty, |impl_id| { - let partial_ord_is_automatically_derived = cx.tcx.has_attr(impl_id, sym::automatically_derived); + let partial_ord_is_automatically_derived = cx.tcx.is_automatically_derived(impl_id); if partial_ord_is_automatically_derived == ord_is_automatically_derived { return; @@ -349,6 +349,10 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &h { return; } + // The presence of `unsafe` fields prevents deriving `Clone` automatically + if ty_adt.all_fields().any(|f| f.safety.is_unsafe()) { + return; + } span_lint_and_note( cx, @@ -426,10 +430,10 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { } fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { - if let ExprKind::Block(block, _) = expr.kind { - if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - return ControlFlow::Break(()); - } + if let ExprKind::Block(block, _) = expr.kind + && block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + { + return ControlFlow::Break(()); } walk_expr(self, expr) @@ -479,7 +483,7 @@ fn ty_implements_eq_trait<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, eq_trait_id: De tcx.non_blanket_impls_for_ty(eq_trait_id, ty).next().is_some() } -/// Creates the `ParamEnv` used for the give type's derived `Eq` impl. +/// Creates the `ParamEnv` used for the given type's derived `Eq` impl. fn typing_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ty::TypingEnv<'_> { // Initial map from generic index to param def. // Vec<(param_def, needs_eq)> diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 4b8a689e994..fc6af204a74 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -4,6 +4,7 @@ use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::macros::macro_backtrace; use rustc_data_structures::fx::FxHashSet; +use rustc_hir::def::DefKind; use rustc_hir::def_id::DefIdMap; use rustc_hir::{ AmbigArg, Expr, ExprKind, ForeignItem, HirId, ImplItem, Item, ItemKind, OwnerId, Pat, Path, Stmt, TraitItem, Ty, @@ -72,8 +73,15 @@ pub struct DisallowedMacros { impl DisallowedMacros { pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { + let (disallowed, _) = create_disallowed_map( + tcx, + &conf.disallowed_macros, + |def_kind| matches!(def_kind, DefKind::Macro(_)), + "macro", + false, + ); Self { - disallowed: create_disallowed_map(tcx, &conf.disallowed_macros), + disallowed, seen: FxHashSet::default(), derive_src: None, earlies, diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 149cf1cf2de..1382dafa931 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -63,9 +63,19 @@ pub struct DisallowedMethods { impl DisallowedMethods { pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { - Self { - disallowed: create_disallowed_map(tcx, &conf.disallowed_methods), - } + let (disallowed, _) = create_disallowed_map( + tcx, + &conf.disallowed_methods, + |def_kind| { + matches!( + def_kind, + DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn + ) + }, + "function", + false, + ); + Self { disallowed } } } @@ -74,12 +84,7 @@ impl_lint_pass!(DisallowedMethods => [DISALLOWED_METHODS]); impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let (id, span) = match &expr.kind { - ExprKind::Path(path) - if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = - cx.qpath_res(path, expr.hir_id) => - { - (id, expr.span) - }, + ExprKind::Path(path) if let Res::Def(_, id) = cx.qpath_res(path, expr.hir_id) => (id, expr.span), ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { (id, name.ident.span) }, diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 38903596414..2bae82648ac 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -1,8 +1,8 @@ use clippy_config::Conf; -use clippy_config::types::DisallowedPath; +use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::span_lint_and_then; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::def::Res; +use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{AmbigArg, Item, ItemKind, PolyTraitRef, PrimTy, Ty, TyKind, UseKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -60,22 +60,7 @@ pub struct DisallowedTypes { impl DisallowedTypes { pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { - let mut def_ids = DefIdMap::default(); - let mut prim_tys = FxHashMap::default(); - for disallowed_path in &conf.disallowed_types { - let path: Vec<_> = disallowed_path.path().split("::").collect::<Vec<_>>(); - for res in clippy_utils::def_path_res(tcx, &path) { - match res { - Res::Def(_, id) => { - def_ids.insert(id, (disallowed_path.path(), disallowed_path)); - }, - Res::PrimTy(ty) => { - prim_tys.insert(ty, (disallowed_path.path(), disallowed_path)); - }, - _ => {}, - } - } - } + let (def_ids, prim_tys) = create_disallowed_map(tcx, &conf.disallowed_types, def_kind_predicate, "type", true); Self { def_ids, prim_tys } } @@ -95,6 +80,19 @@ impl DisallowedTypes { } } +pub fn def_kind_predicate(def_kind: DefKind) -> bool { + matches!( + def_kind, + DefKind::Struct + | DefKind::Union + | DefKind::Enum + | DefKind::Trait + | DefKind::TyAlias + | DefKind::ForeignTy + | DefKind::AssocTy + ) +} + impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 8cdaba88e50..7a1c7c675d2 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -113,20 +113,20 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b s != "-" && s.contains('-') } - if let Ok(url) = Url::parse(word) { + if let Ok(url) = Url::parse(word) // try to get around the fact that `foo::bar` parses as a valid URL - if !url.cannot_be_a_base() { - span_lint_and_sugg( - cx, - DOC_MARKDOWN, - span, - "you should put bare URLs between `<`/`>` or make a proper Markdown link", - "try", - format!("<{word}>"), - Applicability::MachineApplicable, - ); - return; - } + && !url.cannot_be_a_base() + { + span_lint_and_sugg( + cx, + DOC_MARKDOWN, + span, + "you should put bare URLs between `<`/`>` or make a proper Markdown link", + "try", + format!("<{word}>"), + Applicability::MachineApplicable, + ); + return; } // We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343) diff --git a/clippy_lints/src/doc/missing_headers.rs b/clippy_lints/src/doc/missing_headers.rs index e75abf28bac..039937e0207 100644 --- a/clippy_lints/src/doc/missing_headers.rs +++ b/clippy_lints/src/doc/missing_headers.rs @@ -1,11 +1,14 @@ use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC}; use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; -use clippy_utils::ty::{implements_trait_with_env, is_type_diagnostic_item}; -use clippy_utils::{is_doc_hidden, return_ty}; +use clippy_utils::macros::{is_panic, root_macro_call_first_node}; +use clippy_utils::ty::{get_type_diagnostic_name, implements_trait_with_env, is_type_diagnostic_item}; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{fulfill_or_allowed, is_doc_hidden, method_chain_args, return_ty}; use rustc_hir::{BodyId, FnSig, OwnerId, Safety}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::{Span, sym}; +use std::ops::ControlFlow; pub fn check( cx: &LateContext<'_>, @@ -13,7 +16,6 @@ pub fn check( sig: FnSig<'_>, headers: DocHeaders, body_id: Option<BodyId>, - panic_info: Option<(Span, bool)>, check_private_items: bool, ) { if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) { @@ -46,13 +48,16 @@ pub fn check( ), _ => (), } - if !headers.panics && panic_info.is_some_and(|el| !el.1) { + if !headers.panics + && let Some(body_id) = body_id + && let Some(panic_span) = find_panic(cx, body_id) + { span_lint_and_note( cx, MISSING_PANICS_DOC, span, "docs for function which may panic missing `# Panics` section", - panic_info.map(|el| el.0), + Some(panic_span), "first possible panic found here", ); } @@ -89,3 +94,39 @@ pub fn check( } } } + +fn find_panic(cx: &LateContext<'_>, body_id: BodyId) -> Option<Span> { + let mut panic_span = None; + let typeck = cx.tcx.typeck_body(body_id); + for_each_expr(cx, cx.tcx.hir_body(body_id), |expr| { + if let Some(macro_call) = root_macro_call_first_node(cx, expr) + && (is_panic(cx, macro_call.def_id) + || matches!( + cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::assert_macro | sym::assert_eq_macro | sym::assert_ne_macro) + )) + && !cx.tcx.hir_is_inside_const_context(expr.hir_id) + && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) + && panic_span.is_none() + { + panic_span = Some(macro_call.span); + } + + // check for `unwrap` and `expect` for both `Option` and `Result` + if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or_else(|| method_chain_args(expr, &["expect"])) + && let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs() + && matches!( + get_type_diagnostic_name(cx, receiver_ty), + Some(sym::Option | sym::Result) + ) + && !fulfill_or_allowed(cx, MISSING_PANICS_DOC, [expr.hir_id]) + && panic_span.is_none() + { + panic_span = Some(expr.span); + } + + // Visit all nodes to fulfill any `#[expect]`s after the first linted panic + ControlFlow::<!>::Continue(()) + }); + panic_span +} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 36fd396cc1d..ab77edf1147 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -3,11 +3,8 @@ use clippy_config::Conf; use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_then}; -use clippy_utils::macros::{is_panic, root_macro_call_first_node}; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::Visitable; -use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args}; +use clippy_utils::{is_entrypoint_fn, is_trait_impl_item}; use pulldown_cmark::Event::{ Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, TaskListMarker, Text, @@ -16,18 +13,15 @@ use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, It use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{AnonConst, Attribute, Expr, ImplItemKind, ItemKind, Node, Safety, TraitItemKind}; +use rustc_hir::{Attribute, ImplItemKind, ItemKind, Node, Safety, TraitItemKind}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::nested_filter; -use rustc_middle::ty; use rustc_resolve::rustdoc::{ DocFragment, add_doc_fragment, attrs_to_doc_fragments, main_body_opts, source_span_for_markdown_range, span_of_fragments, }; use rustc_session::impl_lint_pass; +use rustc_span::Span; use rustc_span::edition::Edition; -use rustc_span::{Span, sym}; use std::ops::Range; use url::Url; @@ -194,6 +188,19 @@ declare_clippy_lint! { /// } /// } /// ``` + /// + /// Individual panics within a function can be ignored with `#[expect]` or + /// `#[allow]`: + /// + /// ```no_run + /// # use std::num::NonZeroUsize; + /// pub fn will_not_panic(x: usize) { + /// #[expect(clippy::missing_panics_doc, reason = "infallible")] + /// let y = NonZeroUsize::new(1).unwrap(); + /// + /// // If any panics are added in the future the lint will still catch them + /// } + /// ``` #[clippy::version = "1.51.0"] pub MISSING_PANICS_DOC, pedantic, @@ -657,20 +664,16 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { self.check_private_items, ); match item.kind { - ItemKind::Fn { sig, body: body_id, .. } => { + ItemKind::Fn { sig, body, .. } => { if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || item.span.in_external_macro(cx.tcx.sess.source_map())) { - let body = cx.tcx.hir_body(body_id); - - let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value); missing_headers::check( cx, item.owner_id, sig, headers, - Some(body_id), - panic_info, + Some(body), self.check_private_items, ); } @@ -697,15 +700,7 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { if let TraitItemKind::Fn(sig, ..) = trait_item.kind && !trait_item.span.in_external_macro(cx.tcx.sess.source_map()) { - missing_headers::check( - cx, - trait_item.owner_id, - sig, - headers, - None, - None, - self.check_private_items, - ); + missing_headers::check(cx, trait_item.owner_id, sig, headers, None, self.check_private_items); } }, Node::ImplItem(impl_item) => { @@ -713,16 +708,12 @@ impl<'tcx> LateLintPass<'tcx> for Documentation { && !impl_item.span.in_external_macro(cx.tcx.sess.source_map()) && !is_trait_impl_item(cx, impl_item.hir_id()) { - let body = cx.tcx.hir_body(body_id); - - let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(impl_item.owner_id), body.value); missing_headers::check( cx, impl_item.owner_id, sig, headers, Some(body_id), - panic_span, self.check_private_items, ); } @@ -880,19 +871,18 @@ fn check_for_code_clusters<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a if let Some(start) = code_starts_at && let Some(end) = code_ends_at && code_includes_link + && let Some(span) = fragments.span(cx, start..end) { - if let Some(span) = fragments.span(cx, start..end) { - span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| { - let sugg = format!("<code>{}</code>", doc[start..end].replace('`', "")); - diag.span_suggestion_verbose( - span, - "wrap the entire group in `<code>` tags", - sugg, - Applicability::MaybeIncorrect, - ); - diag.help("separate code snippets will be shown with a gap"); - }); - } + span_lint_and_then(cx, DOC_LINK_CODE, span, "code link adjacent to code text", |diag| { + let sugg = format!("<code>{}</code>", doc[start..end].replace('`', "")); + diag.span_suggestion_verbose( + span, + "wrap the entire group in `<code>` tags", + sugg, + Applicability::MaybeIncorrect, + ); + diag.help("separate code snippets will be shown with a gap"); + }); } code_includes_link = false; code_starts_at = None; @@ -1169,72 +1159,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize headers } -struct FindPanicUnwrap<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - is_const: bool, - panic_span: Option<Span>, - typeck_results: &'tcx ty::TypeckResults<'tcx>, -} - -impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> { - pub fn find_span( - cx: &'a LateContext<'tcx>, - typeck_results: &'tcx ty::TypeckResults<'tcx>, - body: impl Visitable<'tcx>, - ) -> Option<(Span, bool)> { - let mut vis = Self { - cx, - is_const: false, - panic_span: None, - typeck_results, - }; - body.visit(&mut vis); - vis.panic_span.map(|el| (el, vis.is_const)) - } -} - -impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> { - type NestedFilter = nested_filter::OnlyBodies; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.panic_span.is_some() { - return; - } - - if let Some(macro_call) = root_macro_call_first_node(self.cx, expr) { - if is_panic(self.cx, macro_call.def_id) - || matches!( - self.cx.tcx.item_name(macro_call.def_id).as_str(), - "assert" | "assert_eq" | "assert_ne" - ) - { - self.is_const = self.cx.tcx.hir_is_inside_const_context(expr.hir_id); - self.panic_span = Some(macro_call.span); - } - } - - // check for `unwrap` and `expect` for both `Option` and `Result` - if let Some(arglists) = method_chain_args(expr, &["unwrap"]).or(method_chain_args(expr, &["expect"])) { - let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(self.cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(self.cx, receiver_ty, sym::Result) - { - self.panic_span = Some(expr.span); - } - } - - // and check sub-expressions - intravisit::walk_expr(self, expr); - } - - // Panics in const blocks will cause compilation to fail. - fn visit_anon_const(&mut self, _: &'tcx AnonConst) {} - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} - #[expect(clippy::range_plus_one)] // inclusive ranges aren't the same type fn looks_like_refdef(doc: &str, range: Range<usize>) -> Option<Range<usize>> { if range.end < range.start { diff --git a/clippy_lints/src/doc/needless_doctest_main.rs b/clippy_lints/src/doc/needless_doctest_main.rs index bf549dcdb50..ec4538039a9 100644 --- a/clippy_lints/src/doc/needless_doctest_main.rs +++ b/clippy_lints/src/doc/needless_doctest_main.rs @@ -64,7 +64,10 @@ pub fn check( match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => match &item.kind { ItemKind::Fn(box Fn { - ident, sig, body: Some(block), .. + ident, + sig, + body: Some(block), + .. }) if ident.name == sym::main => { if !ignore { get_test_spans(&item, *ident, &mut test_attr_spans); diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 617982f4da3..5c360ce6a5f 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -144,10 +144,10 @@ impl<'tcx> LateLintPass<'tcx> for DropForgetRef { // .. // } fn is_single_call_in_arm<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'_>, drop_expr: &'tcx Expr<'_>) -> bool { - if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) { - if let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) { - return body.hir_id == drop_expr.hir_id; - } + if matches!(arg.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) + && let Node::Arm(Arm { body, .. }) = cx.tcx.parent_hir_node(drop_expr.hir_id) + { + return body.hir_id == drop_expr.hir_id; } false } diff --git a/clippy_lints/src/empty_line_after.rs b/clippy_lints/src/empty_line_after.rs index c67dcd3c82b..0c5f8bbf4ca 100644 --- a/clippy_lints/src/empty_line_after.rs +++ b/clippy_lints/src/empty_line_after.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, snippet_indent}; use clippy_utils::tokenize_with_text; @@ -89,7 +91,7 @@ declare_clippy_lint! { #[derive(Debug)] struct ItemInfo { kind: &'static str, - name: Symbol, + name: Option<Symbol>, span: Span, mod_items: Option<NodeId>, } @@ -315,8 +317,12 @@ impl EmptyLineAfter { for stop in gaps.iter().flat_map(|gap| gap.prev_chunk) { stop.comment_out(cx, &mut suggestions); } + let name = match info.name { + Some(name) => format!("{} `{name}`", info.kind).into(), + None => Cow::from("the following item"), + }; diag.multipart_suggestion_verbose( - format!("if the doc comment should not document `{}` comment it out", info.name), + format!("if the doc comment should not document {name} then comment it out"), suggestions, Applicability::MaybeIncorrect, ); @@ -381,13 +387,10 @@ impl EmptyLineAfter { ) { self.items.push(ItemInfo { kind: kind.descr(), - // FIXME: this `sym::empty` can be leaked, see - // https://github.com/rust-lang/rust/pull/138740#discussion_r2021979899 - name: if let Some(ident) = ident { ident.name } else { kw::Empty }, - span: if let Some(ident) = ident { - span.with_hi(ident.span.hi()) - } else { - span.with_hi(span.lo()) + name: ident.map(|ident| ident.name), + span: match ident { + Some(ident) => span.with_hi(ident.span.hi()), + None => span.shrink_to_lo(), }, mod_items: match kind { ItemKind::Mod(_, _, ModKind::Loaded(items, _, _, _)) => items @@ -447,7 +450,7 @@ impl EarlyLintPass for EmptyLineAfter { fn check_crate(&mut self, _: &EarlyContext<'_>, krate: &Crate) { self.items.push(ItemInfo { kind: "crate", - name: kw::Crate, + name: Some(kw::Crate), span: krate.spans.inner_span.with_hi(krate.spans.inner_span.lo()), mod_items: krate .items diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 7d87f04fef9..a38d6df89f2 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -1,10 +1,15 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_opt; -use rustc_ast::ast::{Item, ItemKind, Variant, VariantData}; +use clippy_utils::attrs::span_contains_cfg; +use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; -use rustc_lexer::TokenKind; -use rustc_lint::{EarlyContext, EarlyLintPass}; -use rustc_session::declare_lint_pass; +use rustc_hir::def::CtorOf; +use rustc_hir::def::DefKind::Ctor; +use rustc_hir::def::Res::Def; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyCtxt; +use rustc_session::impl_lint_pass; use rustc_span::Span; declare_clippy_lint! { @@ -70,10 +75,23 @@ declare_clippy_lint! { "finds enum variants with empty brackets" } -declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]); +#[derive(Debug)] +enum Usage { + Unused { redundant_use_sites: Vec<Span> }, + Used, + NoDefinition { redundant_use_sites: Vec<Span> }, +} + +#[derive(Default)] +pub struct EmptyWithBrackets { + // Value holds `Usage::Used` if the empty tuple variant was used as a function + empty_tuple_enum_variants: FxIndexMap<LocalDefId, Usage>, +} + +impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]); -impl EarlyLintPass for EmptyWithBrackets { - fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { +impl LateLintPass<'_> for EmptyWithBrackets { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Struct(ident, var_data, _) = &item.kind && has_brackets(var_data) && let span_after_ident = item.span.with_lo(ident.span.hi()) @@ -96,70 +114,175 @@ impl EarlyLintPass for EmptyWithBrackets { } } - fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) { + fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) { + // the span of the parentheses/braces let span_after_ident = variant.span.with_lo(variant.ident.span.hi()); - if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) { - span_lint_and_then( + if has_no_fields(cx, &variant.data, span_after_ident) { + match variant.data { + VariantData::Struct { .. } => { + // Empty struct variants can be linted immediately + span_lint_and_then( + cx, + EMPTY_ENUM_VARIANTS_WITH_BRACKETS, + span_after_ident, + "enum variant has empty brackets", + |diagnostic| { + diagnostic.span_suggestion_hidden( + span_after_ident, + "remove the brackets", + "", + Applicability::MaybeIncorrect, + ); + }, + ); + }, + VariantData::Tuple(.., local_def_id) => { + // Don't lint reachable tuple enums + if cx.effective_visibilities.is_reachable(variant.def_id) { + return; + } + if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) { + // empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the + // definition was encountered. Now that there's a definition, convert it + // to Usage::Unused. + if let Usage::NoDefinition { redundant_use_sites } = entry { + *entry = Usage::Unused { + redundant_use_sites: redundant_use_sites.clone(), + }; + } + } else { + self.empty_tuple_enum_variants.insert( + local_def_id, + Usage::Unused { + redundant_use_sites: vec![], + }, + ); + } + }, + VariantData::Unit(..) => {}, + } + } + } + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if let Some(def_id) = check_expr_for_enum_as_function(expr) { + if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) { + // Do not count expressions from macro expansion as a redundant use site. + if expr.span.from_expansion() { + return; + } + match self.empty_tuple_enum_variants.get_mut(&def_id) { + Some( + &mut (Usage::Unused { + ref mut redundant_use_sites, + } + | Usage::NoDefinition { + ref mut redundant_use_sites, + }), + ) => { + redundant_use_sites.push(parentheses_span); + }, + None => { + // The variant isn't in the IndexMap which means its definition wasn't encountered yet. + self.empty_tuple_enum_variants.insert( + def_id, + Usage::NoDefinition { + redundant_use_sites: vec![parentheses_span], + }, + ); + }, + _ => {}, + } + } else { + // The parentheses are not redundant. + self.empty_tuple_enum_variants.insert(def_id, Usage::Used); + } + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'_>) { + for (local_def_id, usage) in &self.empty_tuple_enum_variants { + // Ignore all variants with Usage::Used or Usage::NoDefinition + let Usage::Unused { redundant_use_sites } = usage else { + continue; + }; + // Attempt to fetch the Variant from LocalDefId. + let Node::Variant(variant) = cx.tcx.hir_node( + cx.tcx + .local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()), + ) else { + continue; + }; + // Span of the parentheses in variant definition + let span = variant.span.with_lo(variant.ident.span.hi()); + span_lint_hir_and_then( cx, EMPTY_ENUM_VARIANTS_WITH_BRACKETS, - span_after_ident, + variant.hir_id, + span, "enum variant has empty brackets", |diagnostic| { - diagnostic.span_suggestion_hidden( - span_after_ident, - "remove the brackets", - "", - Applicability::MaybeIncorrect, - ); + if redundant_use_sites.is_empty() { + // If there's no redundant use sites, the definition is the only place to modify. + diagnostic.span_suggestion_hidden( + span, + "remove the brackets", + "", + Applicability::MaybeIncorrect, + ); + } else { + let mut parentheses_spans: Vec<_> = + redundant_use_sites.iter().map(|span| (*span, String::new())).collect(); + parentheses_spans.push((span, String::new())); + diagnostic.multipart_suggestion( + "remove the brackets", + parentheses_spans, + Applicability::MaybeIncorrect, + ); + } }, ); } } } -fn has_no_ident_token(braces_span_str: &str) -> bool { - !rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident) +fn has_brackets(var_data: &VariantData<'_>) -> bool { + !matches!(var_data, VariantData::Unit(..)) } -fn has_brackets(var_data: &VariantData) -> bool { - !matches!(var_data, VariantData::Unit(_)) -} - -fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool { - if !var_data.fields().is_empty() { - return false; - } - +fn has_no_fields(cx: &LateContext<'_>, var_data: &VariantData<'_>, braces_span: Span) -> bool { + var_data.fields().is_empty() && // there might still be field declarations hidden from the AST // (conditionally compiled code using #[cfg(..)]) - - let Some(braces_span_str) = snippet_opt(cx, braces_span) else { - return false; - }; - - has_no_ident_token(braces_span_str.as_ref()) + !span_contains_cfg(cx, braces_span) } -#[cfg(test)] -mod unit_test { - use super::*; - - #[test] - fn test_has_no_ident_token() { - let input = "{ field: u8 }"; - assert!(!has_no_ident_token(input)); - - let input = "(u8, String);"; - assert!(!has_no_ident_token(input)); - - let input = " { - // test = 5 - } - "; - assert!(has_no_ident_token(input)); +// If expression HIR ID and callee HIR ID are same, returns the span of the parentheses, else, +// returns None. +fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> { + if let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id) + && let ExprKind::Call(callee, ..) = parent.kind + && callee.hir_id == expr.hir_id + { + Some(parent.span.with_lo(expr.span.hi())) + } else { + None + } +} - let input = " ();"; - assert!(has_no_ident_token(input)); +// Returns the LocalDefId of the variant being called as a function if it exists. +fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> { + if let ExprKind::Path(QPath::Resolved( + _, + Path { + res: Def(Ctor(CtorOf::Variant, _), def_id), + .. + }, + )) = expr.kind + { + def_id.as_local() + } else { + None } } diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index dcfee0b6d3c..182cb4e46d2 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -95,14 +95,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { return; }; - if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy { - span_lint(cx, MAP_ENTRY, expr.span, lint_msg); - return; - } - if then_search.edits.is_empty() && else_search.edits.is_empty() { // No insertions return; + } else if then_search.is_key_used_and_no_copy || else_search.is_key_used_and_no_copy { + // If there are other uses of the key, and the key is not copy, + // we cannot perform a fix automatically, but continue to emit a lint. + None } else if then_search.edits.is_empty() || else_search.edits.is_empty() { // if .. { insert } else { .. } or if .. { .. } else { insert } let ((then_str, entry_kind), else_str) = match (else_search.edits.is_empty(), contains_expr.negated) { @@ -123,10 +122,10 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { snippet_with_applicability(cx, then_expr.span, "{ .. }", &mut app), ), }; - format!( + Some(format!( "if let {}::{entry_kind} = {map_str}.entry({key_str}) {then_str} else {else_str}", map_ty.entry_path(), - ) + )) } else { // if .. { insert } else { insert } let ((then_str, then_entry), (else_str, else_entry)) = if contains_expr.negated { @@ -142,13 +141,13 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { }; let indent_str = snippet_indent(cx, expr.span); let indent_str = indent_str.as_deref().unwrap_or(""); - format!( + Some(format!( "match {map_str}.entry({key_str}) {{\n{indent_str} {entry}::{then_entry} => {}\n\ {indent_str} {entry}::{else_entry} => {}\n{indent_str}}}", reindent_multiline(&then_str, true, Some(4 + indent_str.len())), reindent_multiline(&else_str, true, Some(4 + indent_str.len())), entry = map_ty.entry_path(), - ) + )) } } else { if then_search.edits.is_empty() { @@ -163,17 +162,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { } else { then_search.snippet_occupied(cx, then_expr.span, &mut app) }; - format!( + Some(format!( "if let {}::{entry_kind} = {map_str}.entry({key_str}) {body_str}", map_ty.entry_path(), - ) + )) } else if let Some(insertion) = then_search.as_single_insertion() { let value_str = snippet_with_context(cx, insertion.value.span, then_expr.span.ctxt(), "..", &mut app).0; if contains_expr.negated { if insertion.value.can_have_side_effects() { - format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});") + Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {value_str});")) } else { - format!("{map_str}.entry({key_str}).or_insert({value_str});") + Some(format!("{map_str}.entry({key_str}).or_insert({value_str});")) } } else { // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here. @@ -183,7 +182,7 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { } else { let block_str = then_search.snippet_closure(cx, then_expr.span, &mut app); if contains_expr.negated { - format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});") + Some(format!("{map_str}.entry({key_str}).or_insert_with(|| {block_str});")) } else { // TODO: suggest using `if let Some(v) = map.get_mut(k) { .. }` here. // This would need to be a different lint. @@ -192,7 +191,11 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { } }; - span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app); + if let Some(sugg) = sugg { + span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app); + } else { + span_lint(cx, MAP_ENTRY, expr.span, lint_msg); + } } } diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index f01b5c840d2..ec81294624e 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -49,10 +49,10 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { .ok() .map(|val| rustc_middle::mir::Const::from_value(val, ty)); if let Some(Constant::Int(val)) = constant.and_then(|c| mir_to_const(cx.tcx, c)) { - if let ty::Adt(adt, _) = ty.kind() { - if adt.is_enum() { - ty = adt.repr().discr_type().to_ty(cx.tcx); - } + if let ty::Adt(adt, _) = ty.kind() + && adt.is_enum() + { + ty = adt.repr().discr_type().to_ty(cx.tcx); } match ty.kind() { ty::Int(IntTy::Isize) => { diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index e248f08789b..2cb3b32babe 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -72,10 +72,10 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { _: Span, fn_def_id: LocalDefId, ) { - if let Some(header) = fn_kind.header() { - if header.abi != ExternAbi::Rust { - return; - } + if let Some(header) = fn_kind.header() + && header.abi != ExternAbi::Rust + { + return; } let parent_id = cx @@ -93,12 +93,11 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal { // find `self` ty for this trait if relevant if let ItemKind::Trait(_, _, _, _, _, items) = item.kind { for trait_item in items { - if trait_item.id.owner_id.def_id == fn_def_id { + if trait_item.id.owner_id.def_id == fn_def_id // be sure we have `self` parameter in this function - if trait_item.kind == (AssocItemKind::Fn { has_self: true }) { - trait_self_ty = - Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty()); - } + && trait_item.kind == (AssocItemKind::Fn { has_self: true }) + { + trait_self_ty = Some(TraitRef::identity(cx.tcx, trait_item.id.owner_id.to_def_id()).self_ty()); } } } @@ -142,22 +141,22 @@ fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool { impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> { fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { - if cmt.place.projections.is_empty() { - if let PlaceBase::Local(lid) = cmt.place.base { - // FIXME(rust/#120456) - is `swap_remove` correct? - self.set.swap_remove(&lid); - } + if cmt.place.projections.is_empty() + && let PlaceBase::Local(lid) = cmt.place.base + { + // FIXME(rust/#120456) - is `swap_remove` correct? + self.set.swap_remove(&lid); } } fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) { - if cmt.place.projections.is_empty() { - if let PlaceBase::Local(lid) = cmt.place.base { - // FIXME(rust/#120456) - is `swap_remove` correct? - self.set.swap_remove(&lid); - } + if cmt.place.projections.is_empty() + && let PlaceBase::Local(lid) = cmt.place.base + { + // FIXME(rust/#120456) - is `swap_remove` correct? + self.set.swap_remove(&lid); } } @@ -171,10 +170,11 @@ impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> { // skip if there is a `self` parameter binding to a type // that contains `Self` (i.e.: `self: Box<Self>`), see #4804 - if let Some(trait_self_ty) = self.trait_self_ty { - if self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower && cmt.place.ty().contains(trait_self_ty) { - return; - } + if let Some(trait_self_ty) = self.trait_self_ty + && self.cx.tcx.hir_name(cmt.hir_id) == kw::SelfLower + && cmt.place.ty().contains(trait_self_ty) + { + return; } if is_non_trait_box(cmt.place.ty()) && !self.is_large_box(cmt.place.ty()) { diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index f67d38d932b..c868b782f43 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -75,10 +75,10 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) { - if is_panic(self.lcx, macro_call.def_id) { - self.result.push(expr.span); - } + if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) + && is_panic(self.lcx, macro_call.def_id) + { + self.result.push(expr.span); } // check for `unwrap` diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index daa199779e3..d5f0659f842 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -154,7 +154,7 @@ fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Su }; } - suggestion.maybe_par() + suggestion.maybe_paren() } fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { @@ -165,7 +165,7 @@ fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, ar expr.span, "logarithm for bases 2, 10 and e can be computed more accurately", "consider using", - format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_par()), + format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()), Applicability::MachineApplicable, ); } @@ -228,24 +228,24 @@ fn get_integer_from_float_constant(value: &Constant<'_>) -> Option<i32> { fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { // Check receiver - if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) { - if let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { + if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver) + && let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value { Some("exp") } else if F32(2.0) == value || F64(2.0) == value { Some("exp2") } else { None - } { - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "exponent for bases 2 and e can be computed more accurately", - "consider using", - format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])), - Applicability::MachineApplicable, - ); } + { + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + expr.span, + "exponent for bases 2 and e can be computed more accurately", + "consider using", + format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])), + Applicability::MachineApplicable, + ); } // Check argument @@ -254,13 +254,13 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: ( SUBOPTIMAL_FLOPS, "square-root of a number can be computed more efficiently and accurately", - format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_par()), + format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()), ) } else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value { ( IMPRECISE_FLOPS, "cube-root of a number can be computed more accurately", - format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_par()), + format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()), ) } else if let Some(exponent) = get_integer_from_float_constant(&value) { ( @@ -268,7 +268,7 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: "exponentiation with integer powers can be computed more efficiently", format!( "{}.powi({})", - Sugg::hir(cx, receiver, "..").maybe_par(), + Sugg::hir(cx, receiver, "..").maybe_paren(), numeric_literal::format(&exponent.to_string(), None, false) ), ) @@ -289,55 +289,53 @@ fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: } fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) { - if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) { - if value == Int(2) { - if let Some(parent) = get_parent_expr(cx, expr) { - if let Some(grandparent) = get_parent_expr(cx, parent) { - if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind - { - if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() { - return; - } - } - } + if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) + && value == Int(2) + && let Some(parent) = get_parent_expr(cx, expr) + { + if let Some(grandparent) = get_parent_expr(cx, parent) + && let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = grandparent.kind + && method_name.as_str() == "sqrt" + && detect_hypot(cx, receiver).is_some() + { + return; + } - if let ExprKind::Binary( - Spanned { - node: op @ (BinOpKind::Add | BinOpKind::Sub), - .. - }, - lhs, - rhs, - ) = parent.kind - { - let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; - - // Negate expr if original code has subtraction and expr is on the right side - let maybe_neg_sugg = |expr, hir_id| { - let sugg = Sugg::hir(cx, expr, ".."); - if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { - -sugg - } else { - sugg - } - }; - - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - parent.span, - "multiply and add expressions can be calculated more efficiently and accurately", - "consider using", - format!( - "{}.mul_add({}, {})", - Sugg::hir(cx, receiver, "..").maybe_par(), - maybe_neg_sugg(receiver, expr.hir_id), - maybe_neg_sugg(other_addend, other_addend.hir_id), - ), - Applicability::MachineApplicable, - ); + if let ExprKind::Binary( + Spanned { + node: op @ (BinOpKind::Add | BinOpKind::Sub), + .. + }, + lhs, + rhs, + ) = parent.kind + { + let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs }; + + // Negate expr if original code has subtraction and expr is on the right side + let maybe_neg_sugg = |expr, hir_id| { + let sugg = Sugg::hir(cx, expr, ".."); + if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id { + -sugg + } else { + sugg } - } + }; + + span_lint_and_sugg( + cx, + SUBOPTIMAL_FLOPS, + parent.span, + "multiply and add expressions can be calculated more efficiently and accurately", + "consider using", + format!( + "{}.mul_add({}, {})", + Sugg::hir(cx, receiver, "..").maybe_paren(), + maybe_neg_sugg(receiver, expr.hir_id), + maybe_neg_sugg(other_addend, other_addend.hir_id), + ), + Applicability::MachineApplicable, + ); } } } @@ -371,7 +369,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, lmul_lhs, "..").maybe_par(), + Sugg::hir(cx, lmul_lhs, "..").maybe_paren(), Sugg::hir(cx, rmul_lhs, "..") )); } @@ -403,7 +401,7 @@ fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> { { return Some(format!( "{}.hypot({})", - Sugg::hir(cx, largs_0, "..").maybe_par(), + Sugg::hir(cx, largs_0, "..").maybe_paren(), Sugg::hir(cx, rargs_0, "..") )); } @@ -449,7 +447,7 @@ fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "(e.pow(x) - 1) can be computed more accurately", "consider using", - format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_par()), + format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()), Applicability::MachineApplicable, ); } @@ -483,12 +481,12 @@ fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) { rhs, ) = &expr.kind { - if let Some(parent) = get_parent_expr(cx, expr) { - if let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind { - if method_name.as_str() == "sqrt" && detect_hypot(cx, receiver).is_some() { - return; - } - } + if let Some(parent) = get_parent_expr(cx, expr) + && let ExprKind::MethodCall(PathSegment { ident: method_name, .. }, receiver, ..) = parent.kind + && method_name.as_str() == "sqrt" + && detect_hypot(cx, receiver).is_some() + { + return; } let maybe_neg_sugg = |expr| { @@ -566,15 +564,15 @@ fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// If the two expressions are not negations of each other, then it /// returns None. fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> { - if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind { - if eq_expr_value(cx, expr1_negated, expr2) { - return Some((false, expr2)); - } + if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind + && eq_expr_value(cx, expr1_negated, expr2) + { + return Some((false, expr2)); } - if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind { - if eq_expr_value(cx, expr1, expr2_negated) { - return Some((true, expr1)); - } + if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind + && eq_expr_value(cx, expr1, expr2_negated) + { + return Some((true, expr1)); } None } @@ -591,11 +589,11 @@ fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) { { let positive_abs_sugg = ( "manual implementation of `abs` method", - format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), + format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()), ); let negative_abs_sugg = ( "manual implementation of negation of `abs` method", - format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_par()), + format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()), ); let sugg = if is_testing_positive(cx, cond, body) { if if_expr_positive { @@ -672,7 +670,7 @@ fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) { "consider using", format!( "{}.log({})", - Sugg::hir(cx, largs_self, "..").maybe_par(), + Sugg::hir(cx, largs_self, "..").maybe_paren(), Sugg::hir(cx, rargs_self, ".."), ), Applicability::MachineApplicable, @@ -703,7 +701,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue) && (F32(180_f32) == lvalue || F64(180_f64) == lvalue) { - let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); + let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren()); if let ExprKind::Lit(literal) = mul_lhs.kind && let ast::LitKind::Float(ref value, float_type) = literal.node && float_type == ast::LitFloatType::Unsuffixed @@ -726,7 +724,7 @@ fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) { } else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue) && (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue) { - let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_par()); + let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren()); if let ExprKind::Lit(literal) = mul_lhs.kind && let ast::LitKind::Float(ref value, float_type) = literal.node && float_type == ast::LitFloatType::Unsuffixed diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 5e3f6b6a137..94e66769eb2 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { .into_owned() } else { let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability); - format!("{}.to_string()", sugg.maybe_par()) + format!("{}.to_string()", sugg.maybe_paren()) }; span_useless_format(cx, call_site, sugg, applicability); } diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 06224f57c5c..8a3f8e1c587 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -141,7 +141,7 @@ declare_clippy_lint! { /// format!("{var:.prec$}"); /// ``` /// - /// If allow-mixed-uninlined-format-args is set to false in clippy.toml, + /// If `allow-mixed-uninlined-format-args` is set to `false` in clippy.toml, /// the following code will also trigger the lint: /// ```no_run /// # let var = 42; @@ -159,7 +159,7 @@ declare_clippy_lint! { /// nothing will be suggested, e.g. `println!("{0}={1}", var, 1+2)`. #[clippy::version = "1.66.0"] pub UNINLINED_FORMAT_ARGS, - pedantic, + style, "using non-inlined variables in `format!` calls" } diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index 5b42a40d850..0535ecf5240 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::macros::{FormatArgsStorage, find_format_arg_expr, is_format_macro, root_macro_call_first_node}; -use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators}; +use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators, sym}; use rustc_ast::{FormatArgsPiece, FormatTrait}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; +use rustc_span::Symbol; use rustc_span::symbol::kw; -use rustc_span::{Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -185,13 +185,13 @@ impl FormatImplExpr<'_, '_> { && let trait_name = match placeholder.format_trait { FormatTrait::Display => sym::Display, FormatTrait::Debug => sym::Debug, - FormatTrait::LowerExp => sym!(LowerExp), - FormatTrait::UpperExp => sym!(UpperExp), - FormatTrait::Octal => sym!(Octal), + FormatTrait::LowerExp => sym::LowerExp, + FormatTrait::UpperExp => sym::UpperExp, + FormatTrait::Octal => sym::Octal, FormatTrait::Pointer => sym::Pointer, - FormatTrait::Binary => sym!(Binary), - FormatTrait::LowerHex => sym!(LowerHex), - FormatTrait::UpperHex => sym!(UpperHex), + FormatTrait::Binary => sym::Binary, + FormatTrait::LowerHex => sym::LowerHex, + FormatTrait::UpperHex => sym::UpperHex, } && trait_name == self.format_trait_impl.name && let Ok(index) = placeholder.argument.index diff --git a/clippy_lints/src/formatting.rs b/clippy_lints/src/formatting.rs index c8fe7ac73cb..4b482f7b233 100644 --- a/clippy_lints/src/formatting.rs +++ b/clippy_lints/src/formatting.rs @@ -138,27 +138,28 @@ impl EarlyLintPass for Formatting { /// Implementation of the `SUSPICIOUS_ASSIGNMENT_FORMATTING` lint. fn check_assign(cx: &EarlyContext<'_>, expr: &Expr) { - if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind { - if !lhs.span.from_expansion() && !rhs.span.from_expansion() { - let eq_span = lhs.span.between(rhs.span); - if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind { - if let Some(eq_snippet) = snippet_opt(cx, eq_span) { - let op = op.as_str(); - let eqop_span = lhs.span.between(sub_rhs.span); - if eq_snippet.ends_with('=') { - span_lint_and_note( - cx, - SUSPICIOUS_ASSIGNMENT_FORMATTING, - eqop_span, - format!( - "this looks like you are trying to use `.. {op}= ..`, but you \ + if let ExprKind::Assign(ref lhs, ref rhs, _) = expr.kind + && !lhs.span.from_expansion() + && !rhs.span.from_expansion() + { + let eq_span = lhs.span.between(rhs.span); + if let ExprKind::Unary(op, ref sub_rhs) = rhs.kind + && let Some(eq_snippet) = snippet_opt(cx, eq_span) + { + let op = op.as_str(); + let eqop_span = lhs.span.between(sub_rhs.span); + if eq_snippet.ends_with('=') { + span_lint_and_note( + cx, + SUSPICIOUS_ASSIGNMENT_FORMATTING, + eqop_span, + format!( + "this looks like you are trying to use `.. {op}= ..`, but you \ really are doing `.. = ({op} ..)`" - ), - None, - format!("to remove this lint, use either `{op}=` or `= {op}`"), - ); - } - } + ), + None, + format!("to remove this lint, use either `{op}=` or `= {op}`"), + ); } } } diff --git a/clippy_lints/src/from_str_radix_10.rs b/clippy_lints/src/from_str_radix_10.rs index 7361546153c..25b087e8484 100644 --- a/clippy_lints/src/from_str_radix_10.rs +++ b/clippy_lints/src/from_str_radix_10.rs @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 { }; let sugg = - Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_par(); + Sugg::hir_with_applicability(cx, expr, "<string>", &mut Applicability::MachineApplicable).maybe_paren(); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index 854fe144c29..fa63876410f 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind}; +use rustc_hir::{BlockCheckMode, Body, ExprKind, FnDecl, ImplicitSelfKind, UnsafeSource}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; @@ -40,14 +40,25 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: name }; - // Body must be &(mut) <self_data>.name + // Body must be `&(mut) <self_data>.name`, potentially in an `unsafe` block // self_data is not necessarily self, to also lint sub-getters, etc… let block_expr = if let ExprKind::Block(block, _) = body.value.kind && block.stmts.is_empty() && let Some(block_expr) = block.expr { - block_expr + if let ExprKind::Block(unsafe_block, _) = block_expr.kind + && unsafe_block.stmts.is_empty() + && matches!( + unsafe_block.rules, + BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + ) + && let Some(unsafe_block_expr) = unsafe_block.expr + { + unsafe_block_expr + } else { + block_expr + } } else { return; }; diff --git a/clippy_lints/src/functions/renamed_function_params.rs b/clippy_lints/src/functions/renamed_function_params.rs index 4495aeb5953..2d22bb157a9 100644 --- a/clippy_lints/src/functions/renamed_function_params.rs +++ b/clippy_lints/src/functions/renamed_function_params.rs @@ -59,9 +59,7 @@ impl RenamedFnArgs { let mut renamed: Vec<(Span, String)> = vec![]; debug_assert!(default_idents.size_hint() == current_idents.size_hint()); - while let (Some(default_ident), Some(current_ident)) = - (default_idents.next(), current_idents.next()) - { + while let (Some(default_ident), Some(current_ident)) = (default_idents.next(), current_idents.next()) { let has_name_to_check = |ident: Option<Ident>| { if let Some(ident) = ident && ident.name != kw::Underscore diff --git a/clippy_lints/src/functions/too_many_arguments.rs b/clippy_lints/src/functions/too_many_arguments.rs index 05dc47f6fe5..48d050aa36a 100644 --- a/clippy_lints/src/functions/too_many_arguments.rs +++ b/clippy_lints/src/functions/too_many_arguments.rs @@ -47,16 +47,16 @@ pub(super) fn check_fn( } pub(super) fn check_trait_item(cx: &LateContext<'_>, item: &hir::TraitItem<'_>, too_many_arguments_threshold: u64) { - if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { + if let hir::TraitItemKind::Fn(ref sig, _) = item.kind // don't lint extern functions decls, it's not their fault - if sig.header.abi == ExternAbi::Rust { - check_arg_number( - cx, - sig.decl, - item.span.with_hi(sig.decl.output.span().hi()), - too_many_arguments_threshold, - ); - } + && sig.header.abi == ExternAbi::Rust + { + check_arg_number( + cx, + sig.decl, + item.span.with_hi(sig.decl.output.span().hi()), + too_many_arguments_threshold, + ); } } diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index fbbd33efd02..9e94280fc07 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -94,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { |diag| { let mut app = Applicability::MachineApplicable; let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) - .maybe_par() + .maybe_paren() .to_string(); let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; let method_body = if let Some(first_stmt) = then_block.stmts.first() { diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 5f95464e4d4..076017a247b 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro}; +use clippy_utils::{desugar_await, get_async_closure_expr, get_async_fn_body, is_async_fn, is_from_proc_macro}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -134,6 +134,10 @@ fn lint_implicit_returns( }, ExprKind::Match(_, arms, _) => { + if let Some(await_expr) = desugar_await(expr) { + lint_return(cx, await_expr.hir_id, await_expr.span); + return LintLocation::Inner; + } for arm in arms { let res = lint_implicit_returns( cx, @@ -153,18 +157,18 @@ fn lint_implicit_returns( ExprKind::Loop(block, ..) => { let mut add_return = false; let _: Option<!> = for_each_expr_without_closures(block, |e| { - if let ExprKind::Break(dest, sub_expr) = e.kind { - if dest.target_id.ok() == Some(expr.hir_id) { - if call_site_span.is_none() && e.span.ctxt() == ctxt { - // At this point sub_expr can be `None` in async functions which either diverge, or return - // the unit type. - if let Some(sub_expr) = sub_expr { - lint_break(cx, e.hir_id, e.span, sub_expr.span); - } - } else { - // the break expression is from a macro call, add a return to the loop - add_return = true; + if let ExprKind::Break(dest, sub_expr) = e.kind + && dest.target_id.ok() == Some(expr.hir_id) + { + if call_site_span.is_none() && e.span.ctxt() == ctxt { + // At this point sub_expr can be `None` in async functions which either diverge, or return + // the unit type. + if let Some(sub_expr) = sub_expr { + lint_break(cx, e.hir_id, e.span, sub_expr.span); } + } else { + // the break expression is from a macro call, add a return to the loop + add_return = true; } } ControlFlow::Continue(()) @@ -241,6 +245,8 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { Some(e) => e, None => return, } + } else if let Some(expr) = get_async_closure_expr(cx.tcx, body.value) { + expr } else { body.value }; diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 5d7d3ae3f24..514e72a4868 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -239,7 +239,7 @@ fn check_subtraction( // This part of the condition is voluntarily split from the one before to ensure that // if `snippet_opt` fails, it won't try the next conditions. if (!is_in_const_context(cx) || msrv.meets(cx, msrvs::SATURATING_SUB_CONST)) - && let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_par) + && let Some(big_expr_sugg) = Sugg::hir_opt(cx, big_expr).map(Sugg::maybe_paren) && let Some(little_expr_sugg) = Sugg::hir_opt(cx, little_expr) { let sugg = format!( diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index 430f2411183..6b89abdb036 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -8,7 +8,7 @@ use rustc_hir::{ }; use rustc_hir_analysis::lower_ty; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, ClauseKind, Generics, Ty, TyCtxt}; +use rustc_middle::ty::{self, AssocItem, ClauseKind, Generics, Ty, TyCtxt}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) { assocs .filter_by_name_unhygienic(constraint.ident.name) .next() - .is_some_and(|assoc| assoc.is_type()) + .is_some_and(AssocItem::is_type) }) { emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound); diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index e1dd7872b9d..e6129757e56 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -65,13 +65,13 @@ declare_clippy_lint! { } pub struct InconsistentStructConstructor { - lint_inconsistent_struct_field_initializers: bool, + check_inconsistent_struct_field_initializers: bool, } impl InconsistentStructConstructor { pub fn new(conf: &'static Conf) -> Self { Self { - lint_inconsistent_struct_field_initializers: conf.lint_inconsistent_struct_field_initializers, + check_inconsistent_struct_field_initializers: conf.check_inconsistent_struct_field_initializers, } } } @@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for InconsistentStructConstructor { let all_fields_are_shorthand = fields.iter().all(|f| f.is_shorthand); let applicability = if all_fields_are_shorthand { Applicability::MachineApplicable - } else if self.lint_inconsistent_struct_field_initializers { + } else if self.check_inconsistent_struct_field_initializers { Applicability::MaybeIncorrect } else { return; diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 33431385c7d..99a393b4d53 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -2,13 +2,12 @@ use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; use clippy_utils::ty::{deref_chain, get_adt_inherent_method}; -use clippy_utils::{higher, is_from_proc_macro, is_in_test}; +use clippy_utils::{higher, is_from_proc_macro, is_in_test, sym}; use rustc_ast::ast::RangeLimits; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -136,28 +135,28 @@ impl<'tcx> LateLintPass<'tcx> for IndexingSlicing { let const_range = to_const_range(cx, range, size); - if let (Some(start), _) = const_range { - if start > size { - span_lint( - cx, - OUT_OF_BOUNDS_INDEXING, - range.start.map_or(expr.span, |start| start.span), - "range is out of bounds", - ); - return; - } + if let (Some(start), _) = const_range + && start > size + { + span_lint( + cx, + OUT_OF_BOUNDS_INDEXING, + range.start.map_or(expr.span, |start| start.span), + "range is out of bounds", + ); + return; } - if let (_, Some(end)) = const_range { - if end > size { - span_lint( - cx, - OUT_OF_BOUNDS_INDEXING, - range.end.map_or(expr.span, |end| end.span), - "range is out of bounds", - ); - return; - } + if let (_, Some(end)) = const_range + && end > size + { + span_lint( + cx, + OUT_OF_BOUNDS_INDEXING, + range.end.map_or(expr.span, |end| end.span), + "range is out of bounds", + ); + return; } if let (Some(_), Some(_)) = const_range { @@ -268,7 +267,7 @@ fn ty_has_applicable_get_function<'tcx>( index_expr: &Expr<'_>, ) -> bool { if let ty::Adt(_, _) = array_ty.kind() - && let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym!(get)).map(|m| { + && let Some(get_output_ty) = get_adt_inherent_method(cx, ty, sym::get).map(|m| { cx.tcx .fn_sig(m.def_id) .skip_binder() diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 960b9aa032b..427a1f82555 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -156,11 +156,12 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { .and(cap); } } - 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); - } + if method.ident.name.as_str() == "flat_map" + && args.len() == 1 + && let ExprKind::Closure(&Closure { body, .. }) = args[0].kind + { + let body = cx.tcx.hir_body(body); + return is_infinite(cx, body.value); } Finite }, diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 4ae1119ab3a..91f65d0b79c 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -123,7 +123,7 @@ fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg expr.span, "manual implementation of `Instant::elapsed`", "try", - format!("{}.elapsed()", sugg.maybe_par()), + format!("{}.elapsed()", sugg.maybe_paren()), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index fc575bff7e6..67ce57de254 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -130,14 +130,14 @@ impl IntPlusOne { BinOpKind::Le => "<", _ => return None, }; - if let Some(snippet) = node.span.get_source_text(cx) { - if let Some(other_side_snippet) = other_side.span.get_source_text(cx) { - let rec = match side { - Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")), - Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")), - }; - return rec; - } + if let Some(snippet) = node.span.get_source_text(cx) + && let Some(other_side_snippet) = other_side.span.get_source_text(cx) + { + let rec = match side { + Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")), + Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")), + }; + return rec; } None } @@ -157,10 +157,10 @@ impl IntPlusOne { impl EarlyLintPass for IntPlusOne { fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) { - if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind { - if let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) { - Self::emit_warning(cx, item, rec); - } + if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind + && let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs) + { + Self::emit_warning(cx, item, rec); } } } diff --git a/clippy_lints/src/invalid_upcast_comparisons.rs b/clippy_lints/src/invalid_upcast_comparisons.rs index b42664340d1..b0ecc5d52dd 100644 --- a/clippy_lints/src/invalid_upcast_comparisons.rs +++ b/clippy_lints/src/invalid_upcast_comparisons.rs @@ -91,49 +91,49 @@ fn upcast_comparison_bounds_err<'tcx>( rhs: &'tcx Expr<'_>, invert: bool, ) { - if let Some((lb, ub)) = lhs_bounds { - if let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) { - if rel == Rel::Eq || rel == Rel::Ne { - if norm_rhs_val < lb || norm_rhs_val > ub { - err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); - } - } else if match rel { - Rel::Lt => { - if invert { - norm_rhs_val < lb - } else { - ub < norm_rhs_val - } - }, - Rel::Le => { - if invert { - norm_rhs_val <= lb - } else { - ub <= norm_rhs_val - } - }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, true); - } else if match rel { - Rel::Lt => { - if invert { - norm_rhs_val >= ub - } else { - lb >= norm_rhs_val - } - }, - Rel::Le => { - if invert { - norm_rhs_val > ub - } else { - lb > norm_rhs_val - } - }, - Rel::Eq | Rel::Ne => unreachable!(), - } { - err_upcast_comparison(cx, span, lhs, false); + if let Some((lb, ub)) = lhs_bounds + && let Some(norm_rhs_val) = ConstEvalCtxt::new(cx).eval_full_int(rhs) + { + if rel == Rel::Eq || rel == Rel::Ne { + if norm_rhs_val < lb || norm_rhs_val > ub { + err_upcast_comparison(cx, span, lhs, rel == Rel::Ne); } + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val < lb + } else { + ub < norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val <= lb + } else { + ub <= norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, true); + } else if match rel { + Rel::Lt => { + if invert { + norm_rhs_val >= ub + } else { + lb >= norm_rhs_val + } + }, + Rel::Le => { + if invert { + norm_rhs_val > ub + } else { + lb > norm_rhs_val + } + }, + Rel::Eq | Rel::Ne => unreachable!(), + } { + err_upcast_comparison(cx, span, lhs, false); } } } diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 977fd5fce15..b1271a264b5 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -377,22 +377,21 @@ impl ItemNameRepetitions { "field name starts with the struct's name", ); } - if field_words.len() > item_name_words.len() { + if field_words.len() > item_name_words.len() // lint only if the end is not covered by the start - if field_words + && field_words .iter() .rev() .zip(item_name_words.iter().rev()) .all(|(a, b)| a == b) - { - span_lint_hir( - cx, - STRUCT_FIELD_NAMES, - field.hir_id, - field.span, - "field name ends with the struct's name", - ); - } + { + span_lint_hir( + cx, + STRUCT_FIELD_NAMES, + field.hir_id, + field.span, + "field name ends with the struct's name", + ); } } } @@ -445,57 +444,56 @@ impl LateLintPass<'_> for ItemNameRepetitions { let item_name = ident.name.as_str(); let item_camel = to_camel_case(item_name); - if !item.span.from_expansion() && is_present_in_source(cx, item.span) { - if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules { - // constants don't have surrounding modules - if !mod_camel.is_empty() { - if mod_name == &ident.name - && let ItemKind::Mod(..) = item.kind - && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) - { - span_lint( - cx, - MODULE_INCEPTION, - item.span, - "module has the same name as its containing module", - ); - } + if !item.span.from_expansion() && is_present_in_source(cx, item.span) + && let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules + // constants don't have surrounding modules + && !mod_camel.is_empty() + { + if mod_name == &ident.name + && let ItemKind::Mod(..) = item.kind + && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) + { + span_lint( + cx, + MODULE_INCEPTION, + item.span, + "module has the same name as its containing module", + ); + } - // The `module_name_repetitions` lint should only trigger if the item has the module in its - // name. Having the same name is accepted. - if cx.tcx.visibility(item.owner_id).is_public() - && cx.tcx.visibility(mod_owner_id.def_id).is_public() - && item_camel.len() > mod_camel.len() - { - let matching = count_match_start(mod_camel, &item_camel); - let rmatching = count_match_end(mod_camel, &item_camel); - let nchars = mod_camel.chars().count(); - - let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); - - if matching.char_count == nchars { - match item_camel.chars().nth(nchars) { - Some(c) if is_word_beginning(c) => span_lint( - cx, - MODULE_NAME_REPETITIONS, - ident.span, - "item name starts with its containing module's name", - ), - _ => (), - } - } - if rmatching.char_count == nchars - && !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count]) - { - span_lint( - cx, - MODULE_NAME_REPETITIONS, - ident.span, - "item name ends with its containing module's name", - ); - } + // The `module_name_repetitions` lint should only trigger if the item has the module in its + // name. Having the same name is accepted. + if cx.tcx.visibility(item.owner_id).is_public() + && cx.tcx.visibility(mod_owner_id.def_id).is_public() + && item_camel.len() > mod_camel.len() + { + let matching = count_match_start(mod_camel, &item_camel); + let rmatching = count_match_end(mod_camel, &item_camel); + let nchars = mod_camel.chars().count(); + + let is_word_beginning = |c: char| c == '_' || c.is_uppercase() || c.is_numeric(); + + if matching.char_count == nchars { + match item_camel.chars().nth(nchars) { + Some(c) if is_word_beginning(c) => span_lint( + cx, + MODULE_NAME_REPETITIONS, + ident.span, + "item name starts with its containing module's name", + ), + _ => (), } } + if rmatching.char_count == nchars + && !self.is_allowed_prefix(&item_camel[..item_camel.len() - rmatching.byte_count]) + { + span_lint( + cx, + MODULE_NAME_REPETITIONS, + ident.span, + "item name ends with its containing module's name", + ); + } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 77085d52a32..8c71d34c95f 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -2,13 +2,13 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_the use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; use clippy_utils::ty::implements_trait; -use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, is_trait_method, peel_ref_operators}; +use clippy_utils::{fulfill_or_allowed, get_item_name, get_parent_as_impl, is_trait_method, peel_ref_operators, sym}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::{ - AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, ImplItem, ImplItemKind, + AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg, GenericBound, HirId, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability, Node, OpaqueTyOrigin, PatExprKind, PatKind, PathSegment, PrimTy, QPath, TraitItemRef, TyKind, }; @@ -16,7 +16,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FnSig, Ty}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::symbol::sym; use rustc_span::{Ident, Span, Symbol}; use rustc_trait_selection::traits::supertrait_def_ids; @@ -143,7 +142,6 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { && let Some(ty_id) = cx.qpath_res(ty_path, imp.self_ty.hir_id).opt_def_id() && let Some(local_id) = ty_id.as_local() && let ty_hir_id = cx.tcx.local_def_id_to_hir_id(local_id) - && !is_lint_allowed(cx, LEN_WITHOUT_IS_EMPTY, ty_hir_id) && let Some(output) = parse_len_output(cx, cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder()) { @@ -157,7 +155,17 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { }, _ => return, }; - check_for_is_empty(cx, sig.span, sig.decl.implicit_self, output, ty_id, name, kind); + check_for_is_empty( + cx, + sig.span, + sig.decl.implicit_self, + output, + ty_id, + name, + kind, + item.hir_id(), + ty_hir_id, + ); } } @@ -180,7 +188,7 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { let mut applicability = Applicability::MachineApplicable; let lit1 = peel_ref_operators(cx, lt.init); - let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_par(); + let lit_str = Sugg::hir_with_context(cx, lit1, lt.span.ctxt(), "_", &mut applicability).maybe_paren(); span_lint_and_sugg( cx, @@ -202,7 +210,11 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { expr.span, lhs_expr, peel_ref_operators(cx, rhs_expr), - (method.ident.name == sym::ne).then_some("!").unwrap_or_default(), + if method.ident.name == sym::ne { + "!" + } else { + Default::default() + }, ); } @@ -282,15 +294,10 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden { let mut current_and_super_traits = DefIdSet::default(); fill_trait_set(visited_trait.owner_id.to_def_id(), &mut current_and_super_traits, cx); - let is_empty = sym!(is_empty); - let is_empty_method_found = current_and_super_traits .items() - .flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(is_empty)) - .any(|i| { - i.is_method() - && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1 - }); + .flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(sym::is_empty)) + .any(|i| i.is_method() && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1); if !is_empty_method_found { span_lint( @@ -442,6 +449,7 @@ fn check_is_empty_sig<'tcx>( } /// Checks if the given type has an `is_empty` method with the appropriate signature. +#[expect(clippy::too_many_arguments)] fn check_for_is_empty( cx: &LateContext<'_>, span: Span, @@ -450,6 +458,8 @@ fn check_for_is_empty( impl_ty: DefId, item_name: Symbol, item_kind: &str, + len_method_hir_id: HirId, + ty_decl_hir_id: HirId, ) { // Implementor may be a type alias, in which case we need to get the `DefId` of the aliased type to // find the correct inherent impls. @@ -459,12 +469,11 @@ fn check_for_is_empty( return; }; - let is_empty = Symbol::intern("is_empty"); let is_empty = cx .tcx .inherent_impls(impl_ty) .iter() - .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty)) + .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(sym::is_empty)) .find(|item| item.is_fn()); let (msg, is_empty_span, self_kind) = match is_empty { @@ -505,14 +514,16 @@ fn check_for_is_empty( Some(_) => return, }; - span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| { - if let Some(span) = is_empty_span { - db.span_note(span, "`is_empty` defined here"); - } - if let Some(self_kind) = self_kind { - db.note(output.expected_sig(self_kind)); - } - }); + if !fulfill_or_allowed(cx, LEN_WITHOUT_IS_EMPTY, [len_method_hir_id, ty_decl_hir_id]) { + span_lint_and_then(cx, LEN_WITHOUT_IS_EMPTY, span, msg, |db| { + if let Some(span) = is_empty_span { + db.span_note(span, "`is_empty` defined here"); + } + if let Some(self_kind) = self_kind { + db.note(output.expected_sig(self_kind)); + } + }); + } } fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_>, op: &str, compare_to: u32) { @@ -522,10 +533,10 @@ fn check_cmp(cx: &LateContext<'_>, span: Span, method: &Expr<'_>, lit: &Expr<'_> if let (&ExprKind::MethodCall(method_path, receiver, [], _), ExprKind::Lit(lit)) = (&method.kind, &lit.kind) { // check if we are in an is_empty() method - if let Some(name) = get_item_name(cx, method) { - if name.as_str() == "is_empty" { - return; - } + if let Some(name) = get_item_name(cx, method) + && name.as_str() == "is_empty" + { + return; } check_len(cx, span, method_path.ident.name, receiver, &lit.node, op, compare_to); @@ -572,7 +583,7 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex let mut applicability = Applicability::MachineApplicable; let lit1 = peel_ref_operators(cx, lit1); - let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_par(); + let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_paren(); span_lint_and_sugg( cx, @@ -587,11 +598,11 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex } fn is_empty_string(expr: &Expr<'_>) -> bool { - if let ExprKind::Lit(lit) = expr.kind { - if let LitKind::Str(lit, _) = lit.node { - let lit = lit.as_str(); - return lit.is_empty(); - } + if let ExprKind::Lit(lit) = expr.kind + && let LitKind::Str(lit, _) = lit.node + { + let lit = lit.as_str(); + return lit.is_empty(); } false } @@ -618,11 +629,10 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// Checks the inherent impl's items for an `is_empty(self)` method. fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool { - let is_empty = sym!(is_empty); cx.tcx.inherent_impls(id).iter().any(|imp| { cx.tcx .associated_items(*imp) - .filter_by_name_unhygienic(is_empty) + .filter_by_name_unhygienic(sym::is_empty) .any(|item| is_is_empty(cx, item)) }) } @@ -630,10 +640,9 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn ty_has_is_empty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, depth: usize) -> bool { match ty.kind() { ty::Dynamic(tt, ..) => tt.principal().is_some_and(|principal| { - let is_empty = sym!(is_empty); cx.tcx .associated_items(principal.def_id()) - .filter_by_name_unhygienic(is_empty) + .filter_by_name_unhygienic(sym::is_empty) .any(|item| is_is_empty(cx, item)) }), ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3fe3cd67e16..5fa8f6f4bf3 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -65,7 +65,6 @@ mod declare_clippy_lint; #[macro_use] extern crate clippy_utils; -#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; pub mod ctfe; // Very important lint, do not remove (rust#125116) @@ -205,6 +204,7 @@ mod loops; mod macro_metavars_in_unsafe; mod macro_use; mod main_recursion; +mod manual_abs_diff; mod manual_assert; mod manual_async_fn; mod manual_bits; @@ -226,7 +226,6 @@ mod manual_rotate; mod manual_slice_size_calculation; mod manual_string_new; mod manual_strip; -mod manual_unwrap_or_default; mod map_unit_fn; mod match_result_ok; mod matches; @@ -320,6 +319,7 @@ mod redundant_locals; mod redundant_pub_crate; mod redundant_slicing; mod redundant_static_lifetimes; +mod redundant_test_prefix; mod redundant_type_annotations; mod ref_option_ref; mod ref_patterns; @@ -412,21 +412,6 @@ use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; use utils::attr_collector::{AttrCollector, AttrStorage}; -/// Register all pre expansion lints -/// -/// Pre-expansion lints run before any macro expansion has happened. -/// -/// Note that due to the architecture of the compiler, currently `cfg_attr` attributes on crate -/// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. -/// -/// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { - // NOTE: Do not add any more pre-expansion passes. These should be removed eventually. - store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf))); - - store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))); -} - #[derive(Default)] struct RegistrationGroups { all: Vec<LintId>, @@ -439,8 +424,6 @@ struct RegistrationGroups { restriction: Vec<LintId>, style: Vec<LintId>, suspicious: Vec<LintId>, - #[cfg(feature = "internal")] - internal: Vec<LintId>, } impl RegistrationGroups { @@ -456,8 +439,6 @@ impl RegistrationGroups { store.register_group(true, "clippy::restriction", Some("clippy_restriction"), self.restriction); store.register_group(true, "clippy::style", Some("clippy_style"), self.style); store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), self.suspicious); - #[cfg(feature = "internal")] - store.register_group(true, "clippy::internal", Some("clippy_internal"), self.internal); } } @@ -472,8 +453,6 @@ pub(crate) enum LintCategory { Restriction, Style, Suspicious, - #[cfg(feature = "internal")] - Internal, } #[allow(clippy::enum_glob_use)] @@ -495,8 +474,6 @@ impl LintCategory { Restriction => &mut groups.restriction, Style => &mut groups.style, Suspicious => &mut groups.suspicious, - #[cfg(feature = "internal")] - Internal => &mut groups.internal, } } } @@ -530,8 +507,6 @@ impl LintInfo { Restriction => "restriction", Style => "style", Suspicious => "suspicious", - #[cfg(feature = "internal")] - Internal => "internal", } } } @@ -589,6 +564,13 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_removed(name, reason); } + // NOTE: Do not add any more pre-expansion passes. These should be removed eventually. + // Due to the architecture of the compiler, currently `cfg_attr` attributes on crate + // level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. + store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf))); + + store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))); + let format_args_storage = FormatArgsStorage::default(); let format_args = format_args_storage.clone(); store.register_early_pass(move || { @@ -601,30 +583,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { let attrs = attr_storage.clone(); store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); - // all the internal lints - #[cfg(feature = "internal")] - { - store.register_early_pass(|| { - Box::new(utils::internal_lints::unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths) - }); - store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce)); - store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls)); - store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths)); - store.register_late_pass(|_| { - Box::<utils::internal_lints::interning_defined_symbol::InterningDefinedSymbol>::default() - }); - store.register_late_pass(|_| { - Box::<utils::internal_lints::lint_without_lint_pass::LintWithoutLintPass>::default() - }); - store.register_late_pass(|_| Box::<utils::internal_lints::unnecessary_def_path::UnnecessaryDefPath>::default()); - store.register_late_pass(|_| Box::new(utils::internal_lints::outer_expn_data_pass::OuterExpnDataPass)); - store.register_late_pass(|_| Box::new(utils::internal_lints::msrv_attr_impl::MsrvAttrImpl)); - 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)); store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))); @@ -772,7 +730,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall)); store.register_early_pass(|| Box::new(unused_unit::UnusedUnit)); store.register_late_pass(|_| Box::new(returns::Return)); - store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf)); + store.register_late_pass(move |tcx| Box::new(collapsible_if::CollapsibleIf::new(tcx, conf))); store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); store.register_early_pass(|| Box::new(precedence::Precedence)); store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); @@ -857,7 +815,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(write::Write::new(conf, format_args.clone()))); store.register_late_pass(move |_| Box::new(cargo::Cargo::new(conf))); store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); - store.register_early_pass(|| Box::new(empty_with_brackets::EmptyWithBrackets)); + store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())); store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); store.register_early_pass(|| Box::new(pub_use::PubUse)); store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); @@ -879,6 +837,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); store.register_late_pass(move |_| Box::new(instant_subtraction::InstantSubtraction::new(conf))); store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); + store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable)); @@ -960,7 +919,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); - store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault)); store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed)); store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); @@ -971,7 +929,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); store.register_late_pass(move |_| Box::new(manual_div_ceil::ManualDivCeil::new(conf))); - store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo)); + store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))); store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)); store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); @@ -984,5 +942,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))); store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap)); + store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index dabef18b98a..5ef5e3a44f8 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -150,10 +150,10 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { } = item.kind { check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv); - } else if let ItemKind::Impl(impl_) = item.kind { - if !item.span.from_expansion() { - report_extra_impl_lifetimes(cx, impl_); - } + } else if let ItemKind::Impl(impl_) = item.kind + && !item.span.from_expansion() + { + report_extra_impl_lifetimes(cx, impl_); } } @@ -300,8 +300,8 @@ fn could_use_elision<'tcx>( let input_lts = input_visitor.lts; let output_lts = output_visitor.lts; - if let Some(trait_sig) = trait_sig - && non_elidable_self_type(cx, func, trait_sig.first().copied(), msrv) + if let Some(&[trait_sig]) = trait_sig + && non_elidable_self_type(cx, func, trait_sig, msrv) { return None; } @@ -310,11 +310,11 @@ fn could_use_elision<'tcx>( let body = cx.tcx.hir_body(body_id); let first_ident = body.params.first().and_then(|param| param.pat.simple_ident()); - if non_elidable_self_type(cx, func, Some(first_ident), msrv) { + if non_elidable_self_type(cx, func, first_ident, msrv) { return None; } - let mut checker = BodyLifetimeChecker; + let mut checker = BodyLifetimeChecker::new(cx); if checker.visit_expr(body.value).is_break() { return None; } @@ -384,8 +384,8 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet<LocalDefI } // elision doesn't work for explicit self types before Rust 1.81, see rust-lang/rust#69064 -fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Option<Ident>>, msrv: Msrv) -> bool { - if let Some(Some(ident)) = ident +fn non_elidable_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option<Ident>, msrv: Msrv) -> bool { + if let Some(ident) = ident && ident.name == kw::SelfLower && !func.implicit_self.has_implicit_self() && let Some(self_ty) = func.inputs.first() @@ -911,10 +911,23 @@ fn elision_suggestions( Some(suggestions) } -struct BodyLifetimeChecker; +struct BodyLifetimeChecker<'tcx> { + tcx: TyCtxt<'tcx>, +} -impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker { +impl<'tcx> BodyLifetimeChecker<'tcx> { + fn new(cx: &LateContext<'tcx>) -> Self { + Self { tcx: cx.tcx } + } +} + +impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker<'tcx> { type Result = ControlFlow<()>; + type NestedFilter = middle_nested_filter::OnlyBodies; + + fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { + self.tcx + } // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) -> ControlFlow<()> { if !lifetime.is_anonymous() && lifetime.ident.name != kw::StaticLifetime { diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 805de23408b..7cbfa2d097a 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -387,12 +387,11 @@ impl LiteralDigitGrouping { let first = groups.next().expect("At least one group"); - if radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal { - if let Some(second_size) = groups.next() { - if !groups.all(|i| i == second_size) || first > second_size { - return Err(WarningType::UnusualByteGroupings); - } - } + if (radix == Radix::Binary || radix == Radix::Octal || radix == Radix::Hexadecimal) + && let Some(second_size) = groups.next() + && (!groups.all(|i| i == second_size) || first > second_size) + { + return Err(WarningType::UnusualByteGroupings); } if let Some(second) = groups.next() { diff --git a/clippy_lints/src/literal_string_with_formatting_args.rs b/clippy_lints/src/literal_string_with_formatting_args.rs index 975e6833a35..244e7c95122 100644 --- a/clippy_lints/src/literal_string_with_formatting_args.rs +++ b/clippy_lints/src/literal_string_with_formatting_args.rs @@ -45,15 +45,14 @@ fn emit_lint(cx: &LateContext<'_>, expr: &Expr<'_>, spans: &[(Span, Option<Strin let spans = spans .iter() .filter_map(|(span, name)| { - if let Some(name) = name { + if let Some(name) = name // We need to check that the name is a local. - if !mir + && !mir .var_debug_info .iter() .any(|local| !local.source_info.span.from_expansion() && local.name.as_str() == name) - { - return None; - } + { + return None; } Some(*span) }) diff --git a/clippy_lints/src/loops/char_indices_as_byte_indices.rs b/clippy_lints/src/loops/char_indices_as_byte_indices.rs new file mode 100644 index 00000000000..8916454ada1 --- /dev/null +++ b/clippy_lints/src/loops/char_indices_as_byte_indices.rs @@ -0,0 +1,141 @@ +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::ty::is_type_lang_item; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{eq_expr_value, higher, path_to_local_id}; +use rustc_errors::{Applicability, MultiSpan}; +use rustc_hir::{Expr, ExprKind, LangItem, Node, Pat, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::Ty; +use rustc_span::{Span, sym}; + +use super::CHAR_INDICES_AS_BYTE_INDICES; + +// The list of `str` methods we want to lint that have a `usize` argument representing a byte index. +// Note: `String` also has methods that work with byte indices, +// but they all take `&mut self` and aren't worth considering since the user couldn't have called +// them while the chars iterator is live anyway. +const BYTE_INDEX_METHODS: &[&str] = &[ + "is_char_boundary", + "floor_char_boundary", + "ceil_char_boundary", + "get", + "index", + "index_mut", + "get_mut", + "get_unchecked", + "get_unchecked_mut", + "slice_unchecked", + "slice_mut_unchecked", + "split_at", + "split_at_mut", + "split_at_checked", + "split_at_mut_checked", +]; + +const CONTINUE: ControlFlow<!, ()> = ControlFlow::Continue(()); + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'_>, iterable: &Expr<'_>, body: &'tcx Expr<'tcx>) { + if let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = iterable.kind + && let Some(method_id) = cx.typeck_results().type_dependent_def_id(iterable.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, method_id) + && let ExprKind::MethodCall(_, chars_recv, _, chars_span) = enumerate_recv.kind + && let Some(method_id) = cx.typeck_results().type_dependent_def_id(enumerate_recv.hir_id) + && cx.tcx.is_diagnostic_item(sym::str_chars, method_id) + { + if let PatKind::Tuple([pat, _], _) = pat.kind + && let PatKind::Binding(_, binding_id, ..) = pat.kind + { + // Destructured iterator element `(idx, _)`, look for uses of the binding + for_each_expr(cx, body, |expr| { + if path_to_local_id(expr, binding_id) { + check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv); + } + CONTINUE + }); + } else if let PatKind::Binding(_, binding_id, ..) = pat.kind { + // Bound as a tuple, look for `tup.0` + for_each_expr(cx, body, |expr| { + if let ExprKind::Field(e, field) = expr.kind + && path_to_local_id(e, binding_id) + && field.name == sym::integer(0) + { + check_index_usage(cx, expr, pat, enumerate_span, chars_span, chars_recv); + } + CONTINUE + }); + } + } +} + +fn check_index_usage<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + pat: &Pat<'_>, + enumerate_span: Span, + chars_span: Span, + chars_recv: &Expr<'_>, +) { + let Some(parent_expr) = index_consumed_at(cx, expr) else { + return; + }; + + let is_string_like = |ty: Ty<'_>| ty.is_str() || is_type_lang_item(cx, ty, LangItem::String); + let message = match parent_expr.kind { + ExprKind::MethodCall(segment, recv, ..) + // We currently only lint `str` methods (which `String` can deref to), so a `.is_str()` check is sufficient here + // (contrary to the `ExprKind::Index` case which needs to handle both with `is_string_like` because `String` implements + // `Index` directly and no deref to `str` would happen in that case). + if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_str() + && BYTE_INDEX_METHODS.contains(&segment.ident.name.as_str()) + && eq_expr_value(cx, chars_recv, recv) => + { + "passing a character position to a method that expects a byte index" + }, + ExprKind::Index(target, ..) + if is_string_like(cx.typeck_results().expr_ty_adjusted(target).peel_refs()) + && eq_expr_value(cx, chars_recv, target) => + { + "indexing into a string with a character position where a byte index is expected" + }, + _ => return, + }; + + span_lint_hir_and_then( + cx, + CHAR_INDICES_AS_BYTE_INDICES, + expr.hir_id, + expr.span, + message, + |diag| { + diag.note("a character can take up more than one byte, so they are not interchangeable") + .span_note( + MultiSpan::from_spans(vec![pat.span, enumerate_span]), + "position comes from the enumerate iterator", + ) + .span_suggestion_verbose( + chars_span.to(enumerate_span), + "consider using `.char_indices()` instead", + "char_indices()", + Applicability::MaybeIncorrect, + ); + }, + ); +} + +/// Returns the expression which ultimately consumes the index. +/// This is usually the parent expression, i.e. `.split_at(idx)` for `idx`, +/// but for `.get(..idx)` we want to consider the method call the consuming expression, +/// which requires skipping past the range expression. +fn index_consumed_at<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + for (_, node) in cx.tcx.hir_parent_iter(expr.hir_id) { + match node { + Node::Expr(expr) if higher::Range::hir(expr).is_some() => {}, + Node::ExprField(_) => {}, + Node::Expr(expr) => return Some(expr), + _ => break, + } + } + None +} diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 412c78cc804..d0b26c91ffa 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -2,6 +2,7 @@ use super::EXPLICIT_ITER_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sym; use clippy_utils::ty::{ implements_trait, implements_trait_with_env, is_copy, is_type_lang_item, make_normalized_projection, make_normalized_projection_with_regions, normalize_with_regions, @@ -11,7 +12,6 @@ use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, EarlyBinder, Ty}; -use rustc_span::sym; pub(super) fn check( cx: &LateContext<'_>, @@ -119,7 +119,7 @@ fn is_ref_iterable<'tcx>( && let typing_env = ty::TypingEnv::non_body_analysis(cx.tcx, fn_id) && implements_trait_with_env(cx.tcx, typing_env, req_self_ty, trait_id, Some(fn_id), &[]) && let Some(into_iter_ty) = - make_normalized_projection_with_regions(cx.tcx, typing_env, trait_id, sym!(IntoIter), [req_self_ty]) + make_normalized_projection_with_regions(cx.tcx, typing_env, trait_id, sym::IntoIter, [req_self_ty]) && let req_res_ty = normalize_with_regions(cx.tcx, typing_env, req_res_ty) && into_iter_ty == req_res_ty { @@ -152,7 +152,7 @@ fn is_ref_iterable<'tcx>( // Using by value won't consume anything if implements_trait(cx, self_ty, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) && ty == res_ty { return Some((AdjustKind::None, self_ty)); @@ -169,7 +169,7 @@ fn is_ref_iterable<'tcx>( }; if implements_trait(cx, self_ty, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) && ty == res_ty { return Some((AdjustKind::reborrow(mutbl), self_ty)); @@ -183,7 +183,7 @@ fn is_ref_iterable<'tcx>( let self_ty = Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, self_ty, mutbl); if implements_trait(cx, self_ty, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [self_ty]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [self_ty]) && ty == res_ty { return Some((AdjustKind::borrow(mutbl), self_ty)); @@ -206,7 +206,7 @@ fn is_ref_iterable<'tcx>( && target != self_ty && implements_trait(cx, target, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) && ty == res_ty { Some((AdjustKind::auto_reborrow(mutbl), target)) @@ -224,7 +224,7 @@ fn is_ref_iterable<'tcx>( if is_copy(cx, target) && implements_trait(cx, target, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) && ty == res_ty { Some((AdjustKind::Deref, target)) @@ -242,7 +242,7 @@ fn is_ref_iterable<'tcx>( if self_ty.is_ref() && implements_trait(cx, target, trait_id, &[]) && let Some(ty) = - make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym!(IntoIter), [target]) + make_normalized_projection(cx.tcx, cx.typing_env(), trait_id, sym::IntoIter, [target]) && ty == res_ty { Some((AdjustKind::auto_borrow(mutbl), target)) diff --git a/clippy_lints/src/loops/for_kv_map.rs b/clippy_lints/src/loops/for_kv_map.rs index 185d834beca..e314bc2068b 100644 --- a/clippy_lints/src/loops/for_kv_map.rs +++ b/clippy_lints/src/loops/for_kv_map.rs @@ -13,45 +13,45 @@ use rustc_span::sym; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, arg: &'tcx Expr<'_>, body: &'tcx Expr<'_>) { let pat_span = pat.span; - if let PatKind::Tuple(pat, _) = pat.kind { - if pat.len() == 2 { - let arg_span = arg.span; - let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { - ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { - (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), - (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), - _ => return, - }, + if let PatKind::Tuple(pat, _) = pat.kind + && pat.len() == 2 + { + let arg_span = arg.span; + let (new_pat_span, kind, ty, mutbl) = match *cx.typeck_results().expr_ty(arg).kind() { + ty::Ref(_, ty, mutbl) => match (&pat[0].kind, &pat[1].kind) { + (key, _) if pat_is_wild(cx, key, body) => (pat[1].span, "value", ty, mutbl), + (_, value) if pat_is_wild(cx, value, body) => (pat[0].span, "key", ty, Mutability::Not), _ => return, - }; - let mutbl = match mutbl { - Mutability::Not => "", - Mutability::Mut => "_mut", - }; - let arg = match arg.kind { - ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr, - _ => arg, - }; + }, + _ => return, + }; + let mutbl = match mutbl { + Mutability::Not => "", + Mutability::Mut => "_mut", + }; + let arg = match arg.kind { + ExprKind::AddrOf(BorrowKind::Ref, _, expr) => expr, + _ => arg, + }; - if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) { - span_lint_and_then( - cx, - FOR_KV_MAP, - arg_span, - format!("you seem to want to iterate on a map's {kind}s"), - |diag| { - let map = sugg::Sugg::hir(cx, arg, "map"); - diag.multipart_suggestion( - "use the corresponding method", - vec![ - (pat_span, snippet(cx, new_pat_span, kind).into_owned()), - (arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_par())), - ], - Applicability::MachineApplicable, - ); - }, - ); - } + if is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap) { + span_lint_and_then( + cx, + FOR_KV_MAP, + arg_span, + format!("you seem to want to iterate on a map's {kind}s"), + |diag| { + let map = sugg::Sugg::hir(cx, arg, "map"); + diag.multipart_suggestion( + "use the corresponding method", + vec![ + (pat_span, snippet(cx, new_pat_span, kind).into_owned()), + (arg_span, format!("{}.{kind}s{mutbl}()", map.maybe_paren())), + ], + Applicability::MachineApplicable, + ); + }, + ); } } } diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index aa8a2934f89..35737f3eafe 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -3,6 +3,7 @@ use super::utils::make_iterator_snippet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; +use clippy_utils::usage::contains_return_break_continue_macro; use clippy_utils::{higher, is_res_lang_ctor, path_res, peel_blocks_with_stmt}; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -35,6 +36,7 @@ pub(super) fn check<'tcx>( && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind && is_res_lang_ctor(cx, path_res(cx, ctor), LangItem::OptionSome) && path_res(cx, inner_ret) == Res::Local(binding_id) + && !contains_return_break_continue_macro(cond) && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr) { let mut applicability = Applicability::MachineApplicable; diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index 701567a7d84..d9c4b526da9 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -28,37 +28,37 @@ pub(super) fn check<'tcx>( end: Some(end), limits, }) = higher::Range::hir(arg) - { // the var must be a single name - if let PatKind::Binding(_, canonical_id, _, _) = pat.kind { - let mut starts = vec![Start { - id: canonical_id, - kind: StartKind::Range, - }]; - - // This is one of few ways to return different iterators - // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 - let mut iter_a = None; - let mut iter_b = None; - - if let ExprKind::Block(block, _) = body.kind { - if let Some(loop_counters) = get_loop_counters(cx, block, expr) { - starts.extend(loop_counters); - } - iter_a = Some(get_assignments(block, &starts)); - } else { - iter_b = Some(get_assignment(body)); + && let PatKind::Binding(_, canonical_id, _, _) = pat.kind + { + let mut starts = vec![Start { + id: canonical_id, + kind: StartKind::Range, + }]; + + // This is one of few ways to return different iterators + // derived from: https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators/52064434#52064434 + let mut iter_a = None; + let mut iter_b = None; + + if let ExprKind::Block(block, _) = body.kind { + if let Some(loop_counters) = get_loop_counters(cx, block, expr) { + starts.extend(loop_counters); } + iter_a = Some(get_assignments(block, &starts)); + } else { + iter_b = Some(get_assignment(body)); + } - let assignments = iter_a.into_iter().flatten().chain(iter_b); + let assignments = iter_a.into_iter().flatten().chain(iter_b); - let big_sugg = assignments - // The only statements in the for loops can be indexed assignments from - // indexed retrievals (except increments of loop counters). - .map(|o| { - o.and_then(|(lhs, rhs)| { - let rhs = fetch_cloned_expr(rhs); - if let ExprKind::Index(base_left, idx_left, _) = lhs.kind + let big_sugg = assignments + // The only statements in the for loops can be indexed assignments from + // indexed retrievals (except increments of loop counters). + .map(|o| { + o.and_then(|(lhs, rhs)| { + let rhs = fetch_cloned_expr(rhs); + if let ExprKind::Index(base_left, idx_left, _) = lhs.kind && let ExprKind::Index(base_right, idx_right, _) = rhs.kind && let Some(ty) = get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_left)) && get_slice_like_element_ty(cx, cx.typeck_results().expr_ty(base_right)).is_some() @@ -68,42 +68,41 @@ pub(super) fn check<'tcx>( && !local_used_in(cx, canonical_id, base_right) // Source and destination must be different && path_to_local(base_left) != path_to_local(base_right) - { - Some(( - ty, - IndexExpr { - base: base_left, - idx: start_left, - idx_offset: offset_left, - }, - IndexExpr { - base: base_right, - idx: start_right, - idx_offset: offset_right, - }, - )) - } else { - None - } - }) + { + Some(( + ty, + IndexExpr { + base: base_left, + idx: start_left, + idx_offset: offset_left, + }, + IndexExpr { + base: base_right, + idx: start_right, + idx_offset: offset_right, + }, + )) + } else { + None + } }) - .map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src))) - .collect::<Option<Vec<_>>>() - .filter(|v| !v.is_empty()) - .map(|v| v.join("\n ")); - - if let Some(big_sugg) = big_sugg { - span_lint_and_sugg( - cx, - MANUAL_MEMCPY, - expr.span, - "it looks like you're manually copying between slices", - "try replacing the loop by", - big_sugg, - Applicability::Unspecified, - ); - return true; - } + }) + .map(|o| o.map(|(ty, dst, src)| build_manual_memcpy_suggestion(cx, start, end, limits, ty, &dst, &src))) + .collect::<Option<Vec<_>>>() + .filter(|v| !v.is_empty()) + .map(|v| v.join("\n ")); + + if let Some(big_sugg) = big_sugg { + span_lint_and_sugg( + cx, + MANUAL_MEMCPY, + expr.span, + "it looks like you're manually copying between slices", + "try replacing the loop by", + big_sugg, + Applicability::Unspecified, + ); + return true; } } false @@ -184,7 +183,12 @@ fn build_manual_memcpy_suggestion<'tcx>( { dst_base_str } else { - format!("{dst_base_str}[{}..{}]", dst_offset.maybe_par(), dst_limit.maybe_par()).into() + format!( + "{dst_base_str}[{}..{}]", + dst_offset.maybe_paren(), + dst_limit.maybe_paren() + ) + .into() }; let method_str = if is_copy(cx, elem_ty) { @@ -196,7 +200,12 @@ fn build_manual_memcpy_suggestion<'tcx>( let src = if is_array_length_equal_to_range(cx, start, end, src.base) { src_base_str } else { - format!("{src_base_str}[{}..{}]", src_offset.maybe_par(), src_limit.maybe_par()).into() + format!( + "{src_base_str}[{}..{}]", + src_offset.maybe_paren(), + src_limit.maybe_paren() + ) + .into() }; format!("{dst}.{method_str}(&{src});") diff --git a/clippy_lints/src/loops/manual_while_let_some.rs b/clippy_lints/src/loops/manual_while_let_some.rs index 4473a3343c7..9527e258db8 100644 --- a/clippy_lints/src/loops/manual_while_let_some.rs +++ b/clippy_lints/src/loops/manual_while_let_some.rs @@ -81,15 +81,15 @@ fn check_local(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, } fn check_call_arguments(cx: &LateContext<'_>, stmt: &Stmt<'_>, is_empty_recv: &Expr<'_>, loop_span: Span) { - if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind { - if let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind { - let offending_arg = args - .iter() - .find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span)); + if let StmtKind::Semi(expr) | StmtKind::Expr(expr) = stmt.kind + && let ExprKind::MethodCall(.., args, _) | ExprKind::Call(_, args) = expr.kind + { + let offending_arg = args + .iter() + .find_map(|arg| is_vec_pop_unwrap(cx, arg, is_empty_recv).then_some(arg.span)); - if let Some(offending_arg) = offending_arg { - report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span); - } + if let Some(offending_arg) = offending_arg { + report_lint(cx, offending_arg, PopStmt::Anonymous, loop_span, is_empty_recv.span); } } } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 4b0bf5a4b3c..2b66827e82e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -1,3 +1,4 @@ +mod char_indices_as_byte_indices; mod empty_loop; mod explicit_counter_loop; mod explicit_into_iter_loop; @@ -740,6 +741,49 @@ declare_clippy_lint! { "manually filling a slice with a value" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of a character position yielded by `.chars().enumerate()` in a context where a **byte index** is expected, + /// such as an argument to a specific `str` method or indexing into a `str` or `String`. + /// + /// ### Why is this bad? + /// A character (more specifically, a Unicode scalar value) that is yielded by `str::chars` can take up multiple bytes, + /// so a character position does not necessarily have the same byte index at which the character is stored. + /// Thus, using the character position where a byte index is expected can unexpectedly return wrong values + /// or panic when the string consists of multibyte characters. + /// + /// For example, the character `a` in `äa` is stored at byte index 2 but has the character position 1. + /// Using the character position 1 to index into the string will lead to a panic as it is in the middle of the first character. + /// + /// Instead of `.chars().enumerate()`, the correct iterator to use is `.char_indices()`, which yields byte indices. + /// + /// This pattern is technically fine if the strings are known to only use the ASCII subset, + /// though in those cases it would be better to use `bytes()` directly to make the intent clearer, + /// but there is also no downside to just using `.char_indices()` directly and supporting non-ASCII strings. + /// + /// You may also want to read the [chapter on strings in the Rust Book](https://doc.rust-lang.org/book/ch08-02-strings.html) + /// which goes into this in more detail. + /// + /// ### Example + /// ```no_run + /// # let s = "..."; + /// for (idx, c) in s.chars().enumerate() { + /// let _ = s[idx..]; // ⚠️ Panics for strings consisting of multibyte characters + /// } + /// ``` + /// Use instead: + /// ```no_run + /// # let s = "..."; + /// for (idx, c) in s.char_indices() { + /// let _ = s[idx..]; + /// } + /// ``` + #[clippy::version = "1.83.0"] + pub CHAR_INDICES_AS_BYTE_INDICES, + correctness, + "using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected" +} + pub struct Loops { msrv: Msrv, enforce_iter_loop_reborrow: bool, @@ -777,6 +821,7 @@ impl_lint_pass!(Loops => [ UNUSED_ENUMERATE_INDEX, INFINITE_LOOP, MANUAL_SLICE_FILL, + CHAR_INDICES_AS_BYTE_INDICES, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -860,6 +905,7 @@ impl Loops { manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); unused_enumerate_index::check(cx, pat, arg, body); + char_indices_as_byte_indices::check(cx, pat, arg, body); } fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) { diff --git a/clippy_lints/src/loops/mut_range_bound.rs b/clippy_lints/src/loops/mut_range_bound.rs index a24dd44790e..70ca452013f 100644 --- a/clippy_lints/src/loops/mut_range_bound.rs +++ b/clippy_lints/src/loops/mut_range_bound.rs @@ -82,14 +82,14 @@ impl<'tcx> Delegate<'tcx> for MutatePairDelegate<'_, 'tcx> { fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { - if bk == ty::BorrowKind::Mutable { - if let PlaceBase::Local(id) = cmt.place.base { - if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { - self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id)); - } - if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { - self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id)); - } + if bk == ty::BorrowKind::Mutable + && let PlaceBase::Local(id) = cmt.place.base + { + if Some(id) == self.hir_id_low && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { + self.span_low = Some(self.cx.tcx.hir_span(diag_expr_id)); + } + if Some(id) == self.hir_id_high && !BreakAfterExprVisitor::is_found(self.cx, diag_expr_id) { + self.span_high = Some(self.cx.tcx.hir_span(diag_expr_id)); } } } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 0f62183eb33..7837b18bcd3 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -31,155 +31,154 @@ pub(super) fn check<'tcx>( ref end, limits, }) = higher::Range::hir(arg) - { // the var must be a single name - if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind { - let mut visitor = VarVisitor { - cx, - var: canonical_id, - indexed_mut: FxHashSet::default(), - indexed_indirectly: FxHashMap::default(), - indexed_directly: FxIndexMap::default(), - referenced: FxHashSet::default(), - nonindex: false, - prefer_mutable: false, - }; - walk_expr(&mut visitor, body); - - // linting condition: we only indexed one variable, and indexed it directly - if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { - let (indexed, (indexed_extent, indexed_ty)) = visitor - .indexed_directly - .into_iter() - .next() - .expect("already checked that we have exactly 1 element"); + && let PatKind::Binding(_, canonical_id, ident, _) = pat.kind + { + let mut visitor = VarVisitor { + cx, + var: canonical_id, + indexed_mut: FxHashSet::default(), + indexed_indirectly: FxHashMap::default(), + indexed_directly: FxIndexMap::default(), + referenced: FxHashSet::default(), + nonindex: false, + prefer_mutable: false, + }; + walk_expr(&mut visitor, body); - // ensure that the indexed variable was declared before the loop, see #601 - if let Some(indexed_extent) = indexed_extent { - let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id); - let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); - let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); - if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { - return; - } - } + // linting condition: we only indexed one variable, and indexed it directly + if visitor.indexed_indirectly.is_empty() && visitor.indexed_directly.len() == 1 { + let (indexed, (indexed_extent, indexed_ty)) = visitor + .indexed_directly + .into_iter() + .next() + .expect("already checked that we have exactly 1 element"); - // don't lint if the container that is indexed does not have .iter() method - let has_iter = has_iter_method(cx, indexed_ty); - if has_iter.is_none() { + // ensure that the indexed variable was declared before the loop, see #601 + if let Some(indexed_extent) = indexed_extent { + let parent_def_id = cx.tcx.hir_get_parent_item(expr.hir_id); + let region_scope_tree = cx.tcx.region_scope_tree(parent_def_id); + let pat_extent = region_scope_tree.var_scope(pat.hir_id.local_id).unwrap(); + if region_scope_tree.is_subscope_of(indexed_extent, pat_extent) { return; } + } - // don't lint if the container that is indexed into is also used without - // indexing - if visitor.referenced.contains(&indexed) { - return; - } + // don't lint if the container that is indexed does not have .iter() method + let has_iter = has_iter_method(cx, indexed_ty); + if has_iter.is_none() { + return; + } - let starts_at_zero = is_integer_const(cx, start, 0); + // don't lint if the container that is indexed into is also used without + // indexing + if visitor.referenced.contains(&indexed) { + return; + } - let skip = if starts_at_zero { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) { - return; - } else { - format!(".skip({})", snippet(cx, start.span, "..")) - }; + let starts_at_zero = is_integer_const(cx, start, 0); - let mut end_is_start_plus_val = false; + let skip = if starts_at_zero { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, start, cx) { + return; + } else { + format!(".skip({})", snippet(cx, start.span, "..")) + }; - let take = if let Some(end) = *end { - let mut take_expr = end; + let mut end_is_start_plus_val = false; - if let ExprKind::Binary(ref op, left, right) = end.kind { - if op.node == BinOpKind::Add { - let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); - let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); + let take = if let Some(end) = *end { + let mut take_expr = end; - if start_equal_left { - take_expr = right; - } else if start_equal_right { - take_expr = left; - } + if let ExprKind::Binary(ref op, left, right) = end.kind + && op.node == BinOpKind::Add + { + let start_equal_left = SpanlessEq::new(cx).eq_expr(start, left); + let start_equal_right = SpanlessEq::new(cx).eq_expr(start, right); - end_is_start_plus_val = start_equal_left | start_equal_right; - } + if start_equal_left { + take_expr = right; + } else if start_equal_right { + take_expr = left; } - if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { - String::new() - } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) { - return; - } else { - match limits { - ast::RangeLimits::Closed => { - let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>"); - format!(".take({})", take_expr + sugg::ONE) - }, - ast::RangeLimits::HalfOpen => { - format!(".take({})", snippet(cx, take_expr.span, "..")) - }, - } - } - } else { - String::new() - }; + end_is_start_plus_val = start_equal_left | start_equal_right; + } - let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { - ("mut ", "iter_mut") + if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) { + String::new() + } else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr, cx) { + return; } else { - ("", "iter") - }; + match limits { + ast::RangeLimits::Closed => { + let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>"); + format!(".take({})", take_expr + sugg::ONE) + }, + ast::RangeLimits::HalfOpen => { + format!(".take({})", snippet(cx, take_expr.span, "..")) + }, + } + } + } else { + String::new() + }; - let take_is_empty = take.is_empty(); - let mut method_1 = take; - let mut method_2 = skip; + let (ref_mut, method) = if visitor.indexed_mut.contains(&indexed) { + ("mut ", "iter_mut") + } else { + ("", "iter") + }; - if end_is_start_plus_val { - mem::swap(&mut method_1, &mut method_2); - } + let take_is_empty = take.is_empty(); + let mut method_1 = take; + let mut method_2 = skip; - if visitor.nonindex { - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - arg.span, - format!("the loop variable `{}` is used to index `{indexed}`", ident.name), - |diag| { - diag.multipart_suggestion( - "consider using an iterator and enumerate()", - vec![ - (pat.span, format!("({}, <item>)", ident.name)), - ( - arg.span, - format!("{indexed}.{method}().enumerate(){method_1}{method_2}"), - ), - ], - Applicability::HasPlaceholders, - ); - }, - ); + if end_is_start_plus_val { + mem::swap(&mut method_1, &mut method_2); + } + + if visitor.nonindex { + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + arg.span, + format!("the loop variable `{}` is used to index `{indexed}`", ident.name), + |diag| { + diag.multipart_suggestion( + "consider using an iterator and enumerate()", + vec![ + (pat.span, format!("({}, <item>)", ident.name)), + ( + arg.span, + format!("{indexed}.{method}().enumerate(){method_1}{method_2}"), + ), + ], + Applicability::HasPlaceholders, + ); + }, + ); + } else { + let repl = if starts_at_zero && take_is_empty { + format!("&{ref_mut}{indexed}") } else { - let repl = if starts_at_zero && take_is_empty { - format!("&{ref_mut}{indexed}") - } else { - format!("{indexed}.{method}(){method_1}{method_2}") - }; + format!("{indexed}.{method}(){method_1}{method_2}") + }; - span_lint_and_then( - cx, - NEEDLESS_RANGE_LOOP, - arg.span, - format!("the loop variable `{}` is only used to index `{indexed}`", ident.name), - |diag| { - diag.multipart_suggestion( - "consider using an iterator", - vec![(pat.span, "<item>".to_string()), (arg.span, repl)], - Applicability::HasPlaceholders, - ); - }, - ); - } + span_lint_and_then( + cx, + NEEDLESS_RANGE_LOOP, + arg.span, + format!("the loop variable `{}` is only used to index `{indexed}`", ident.name), + |diag| { + diag.multipart_suggestion( + "consider using an iterator", + vec![(pat.span, "<item>".to_string()), (arg.span, repl)], + Applicability::HasPlaceholders, + ); + }, + ); } } } @@ -346,10 +345,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { for expr in args { let ty = self.cx.typeck_results().expr_ty_adjusted(expr); self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } + if let ty::Ref(_, _, mutbl) = *ty.kind() + && mutbl == Mutability::Mut + { + self.prefer_mutable = true; } self.visit_expr(expr); } @@ -361,10 +360,10 @@ impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { iter::once(receiver).chain(args.iter()), ) { self.prefer_mutable = false; - if let ty::Ref(_, _, mutbl) = *ty.kind() { - if mutbl == Mutability::Mut { - self.prefer_mutable = true; - } + if let ty::Ref(_, _, mutbl) = *ty.kind() + && mutbl == Mutability::Mut + { + self.prefer_mutable = true; } self.visit_expr(expr); } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index c3a2a38b5ec..69c84bc7038 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -244,10 +244,10 @@ fn never_loop_expr<'tcx>( }); combine_seq(first, || { // checks if break targets a block instead of a loop - if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind { - if let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) { - *reachable = true; - } + if let ExprKind::Break(Destination { target_id: Ok(t), .. }, _) = expr.kind + && let Some((_, reachable)) = local_labels.iter_mut().find(|(label, _)| *label == t) + { + *reachable = true; } NeverLoopResult::Diverging }) diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 3c6fbef2bda..2f6950b4380 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -3,9 +3,7 @@ use clippy_utils::{get_parent_expr, is_integer_const, path_to_local, path_to_loc use rustc_ast::ast::{LitIntType, LitKind}; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr, walk_local}; -use rustc_hir::{ - AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind -}; +use rustc_hir::{AssignOpKind, BorrowKind, Expr, ExprKind, HirId, HirIdMap, LetStmt, Mutability, PatKind}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; @@ -265,7 +263,7 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic if impls_iterator { format!( "{}", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren() ) } else { // (&x).into_iter() ==> x.iter() @@ -283,12 +281,12 @@ pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic }; format!( "{}.{method_name}()", - sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_par(), + sugg::Sugg::hir_with_applicability(cx, caller, "_", applic_ref).maybe_paren(), ) }, _ => format!( "{}.into_iter()", - sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_par() + sugg::Sugg::hir_with_applicability(cx, arg, "_", applic_ref).maybe_paren() ), } } diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index df6e85611fb..9071c9c95f9 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -5,7 +5,8 @@ use itertools::Itertools; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; +use rustc_middle::lint::LevelAndSource; use rustc_session::impl_lint_pass; use rustc_span::{Span, SyntaxContext, sym}; use std::collections::BTreeMap; @@ -249,6 +250,20 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe { }) .flatten() .copied() + .inspect(|&unsafe_block| { + if let LevelAndSource { + level: Level::Expect, + lint_id: Some(id), + .. + } = cx.tcx.lint_level_at_node(MACRO_METAVARS_IN_UNSAFE, unsafe_block) + { + // Since we're going to deduplicate expanded unsafe blocks by its enclosing macro definition soon, + // which would lead to unfulfilled `#[expect()]`s in all other unsafe blocks that are filtered out + // except for the one we emit the warning at, we must manually fulfill the lint + // for all unsafe blocks here. + cx.fulfill_expectation(id); + } + }) .map(|id| { // Remove the syntax context to hide "in this macro invocation" in the diagnostic. // The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything diff --git a/clippy_lints/src/manual_abs_diff.rs b/clippy_lints/src/manual_abs_diff.rs new file mode 100644 index 00000000000..c515e41f242 --- /dev/null +++ b/clippy_lints/src/manual_abs_diff.rs @@ -0,0 +1,152 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::higher::If; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::HasSession as _; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{eq_expr_value, peel_blocks, span_contains_comment}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::impl_lint_pass; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Detects patterns like `if a > b { a - b } else { b - a }` and suggests using `a.abs_diff(b)`. + /// + /// ### Why is this bad? + /// Using `abs_diff` is shorter, more readable, and avoids control flow. + /// + /// ### Examples + /// ```no_run + /// # let (a, b) = (5_usize, 3_usize); + /// if a > b { + /// a - b + /// } else { + /// b - a + /// } + /// # ; + /// ``` + /// Use instead: + /// ```no_run + /// # let (a, b) = (5_usize, 3_usize); + /// a.abs_diff(b) + /// # ; + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_ABS_DIFF, + complexity, + "using an if-else pattern instead of `abs_diff`" +} + +impl_lint_pass!(ManualAbsDiff => [MANUAL_ABS_DIFF]); + +pub struct ManualAbsDiff { + msrv: Msrv, +} + +impl ManualAbsDiff { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } +} + +impl<'tcx> LateLintPass<'tcx> for ManualAbsDiff { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if !expr.span.from_expansion() + && let Some(if_expr) = If::hir(expr) + && let Some(r#else) = if_expr.r#else + && let ExprKind::Binary(op, rhs, lhs) = if_expr.cond.kind + && let (BinOpKind::Gt | BinOpKind::Ge, mut a, mut b) | (BinOpKind::Lt | BinOpKind::Le, mut b, mut a) = + (op.node, rhs, lhs) + && let Some(ty) = self.are_ty_eligible(cx, a, b) + && is_sub_expr(cx, if_expr.then, a, b, ty) + && is_sub_expr(cx, r#else, b, a, ty) + { + span_lint_and_then( + cx, + MANUAL_ABS_DIFF, + expr.span, + "manual absolute difference pattern without using `abs_diff`", + |diag| { + if is_unsuffixed_numeral_lit(a) && !is_unsuffixed_numeral_lit(b) { + (a, b) = (b, a); + } + let applicability = { + let source_map = cx.sess().source_map(); + if span_contains_comment(source_map, if_expr.then.span) + || span_contains_comment(source_map, r#else.span) + { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + } + }; + let sugg = format!( + "{}.abs_diff({})", + Sugg::hir(cx, a, "..").maybe_paren(), + Sugg::hir(cx, b, "..") + ); + diag.span_suggestion(expr.span, "replace with `abs_diff`", sugg, applicability); + }, + ); + } + } +} + +impl ManualAbsDiff { + /// Returns a type if `a` and `b` are both of it, and this lint can be applied to that + /// type (currently, any primitive int, or a `Duration`) + fn are_ty_eligible<'tcx>(&self, cx: &LateContext<'tcx>, a: &Expr<'_>, b: &Expr<'_>) -> Option<Ty<'tcx>> { + let is_int = |ty: Ty<'_>| matches!(ty.kind(), ty::Uint(_) | ty::Int(_)) && self.msrv.meets(cx, msrvs::ABS_DIFF); + let is_duration = + |ty| is_type_diagnostic_item(cx, ty, sym::Duration) && self.msrv.meets(cx, msrvs::DURATION_ABS_DIFF); + + let a_ty = cx.typeck_results().expr_ty(a).peel_refs(); + (a_ty == cx.typeck_results().expr_ty(b).peel_refs() && (is_int(a_ty) || is_duration(a_ty))).then_some(a_ty) + } +} + +/// Checks if the given expression is a subtraction operation between two expected expressions, +/// i.e. if `expr` is `{expected_a} - {expected_b}`. +/// +/// If `expected_ty` is a signed primitive integer, this function will only return `Some` if the +/// subtraction expr is wrapped in a cast to the equivalent unsigned int. +fn is_sub_expr( + cx: &LateContext<'_>, + expr: &Expr<'_>, + expected_a: &Expr<'_>, + expected_b: &Expr<'_>, + expected_ty: Ty<'_>, +) -> bool { + let expr = peel_blocks(expr).kind; + + if let ty::Int(ty) = expected_ty.kind() { + let unsigned = Ty::new_uint(cx.tcx, ty.to_unsigned()); + + return if let ExprKind::Cast(expr, cast_ty) = expr + && cx.typeck_results().node_type(cast_ty.hir_id) == unsigned + { + is_sub_expr(cx, expr, expected_a, expected_b, unsigned) + } else { + false + }; + } + + if let ExprKind::Binary(op, a, b) = expr + && let BinOpKind::Sub = op.node + && eq_expr_value(cx, a, expected_a) + && eq_expr_value(cx, b, expected_b) + { + true + } else { + false + } +} + +fn is_unsuffixed_numeral_lit(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Lit(lit) if lit.node.is_numeric() && lit.node.is_unsuffixed()) +} diff --git a/clippy_lints/src/manual_assert.rs b/clippy_lints/src/manual_assert.rs index 83c16d4466d..8378e15c581 100644 --- a/clippy_lints/src/manual_assert.rs +++ b/clippy_lints/src/manual_assert.rs @@ -60,7 +60,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert { ExprKind::Unary(UnOp::Not, e) => (e, ""), _ => (cond, "!"), }; - let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_par(); + let cond_sugg = sugg::Sugg::hir_with_applicability(cx, cond, "..", &mut applicability).maybe_paren(); let semicolon = if is_parent_stmt(cx, expr.hir_id) { ";" } else { "" }; let sugg = format!("assert!({not}{cond_sugg}, {format_args_snip}){semicolon}"); // we show to the user the suggestion without the comments, but when applying the fix, include the diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 50c8331eeba..02afe9f0997 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -181,7 +181,7 @@ fn maybe_emit_suggestion<'tcx>(cx: &LateContext<'tcx>, suggestion: &ClampSuggest make_assignment, hir_with_ignore_attr, } = suggestion; - let input = Sugg::hir(cx, input, "..").maybe_par(); + let input = Sugg::hir(cx, input, "..").maybe_paren(); let min = Sugg::hir(cx, min, ".."); let max = Sugg::hir(cx, max, ".."); let semicolon = if make_assignment.is_some() { ";" } else { "" }; diff --git a/clippy_lints/src/manual_div_ceil.rs b/clippy_lints/src/manual_div_ceil.rs index 9944c4f8804..444ecd5d2bb 100644 --- a/clippy_lints/src/manual_div_ceil.rs +++ b/clippy_lints/src/manual_div_ceil.rs @@ -1,8 +1,9 @@ -use clippy_utils::SpanlessEq; +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::{Sugg, has_enclosing_paren}; +use clippy_utils::{SpanlessEq, sym}; use rustc_ast::{BinOpKind, LitIntType, LitKind, UnOp}; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; @@ -11,9 +12,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self}; use rustc_session::impl_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::symbol::Symbol; - -use clippy_config::Conf; declare_clippy_lint! { /// ### What it does @@ -141,8 +139,7 @@ fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let expr_ty = cx.typeck_results().expr_ty(expr); match expr_ty.peel_refs().kind() { ty::Uint(_) => true, - ty::Int(_) => cx.tcx.features().enabled(Symbol::intern("int_roundings")), - + ty::Int(_) => cx.tcx.features().enabled(sym::int_roundings), _ => false, } } @@ -167,7 +164,7 @@ fn build_suggestion( rhs: &Expr<'_>, applicability: &mut Applicability, ) { - let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_par(); + let dividend_sugg = Sugg::hir_with_applicability(cx, lhs, "..", applicability).maybe_paren(); let type_suffix = if cx.typeck_results().expr_ty(lhs).is_numeric() && matches!( lhs.kind, diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index faf01a276a1..8ab49bd2ea8 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -148,7 +148,7 @@ fn check_is_ascii( }; let default_snip = ".."; let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par(); + let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren(); let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))]; if let Some((ty_span, ty)) = ty_sugg { suggestion.push((ty_span, format!("{recv}: {ty}"))); diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs index 841adfec462..b4cd988329d 100644 --- a/clippy_lints/src/manual_is_power_of_two.rs +++ b/clippy_lints/src/manual_is_power_of_two.rs @@ -1,13 +1,14 @@ -use clippy_utils::SpanlessEq; +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; -use rustc_ast::LitKind; -use rustc_data_structures::packed::Pu128; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::ty_from_hir_ty; +use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal}; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Uint; -use rustc_session::declare_lint_pass; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does @@ -33,112 +34,111 @@ declare_clippy_lint! { "manually reimplementing `is_power_of_two`" } -declare_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]); +pub struct ManualIsPowerOfTwo { + msrv: Msrv, +} -impl LateLintPass<'_> for ManualIsPowerOfTwo { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - let mut applicability = Applicability::MachineApplicable; +impl_lint_pass!(ManualIsPowerOfTwo => [MANUAL_IS_POWER_OF_TWO]); - if let ExprKind::Binary(bin_op, left, right) = expr.kind - && bin_op.node == BinOpKind::Eq - { - // a.count_ones() == 1 - if let ExprKind::MethodCall(method_name, receiver, [], _) = left.kind - && method_name.ident.as_str() == "count_ones" - && let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind() - && check_lit(right, 1) - { - build_sugg(cx, expr, receiver, &mut applicability); - } +impl ManualIsPowerOfTwo { + pub fn new(conf: &'static Conf) -> Self { + Self { msrv: conf.msrv } + } - // 1 == a.count_ones() - if let ExprKind::MethodCall(method_name, receiver, [], _) = right.kind - && method_name.ident.as_str() == "count_ones" - && let &Uint(_) = cx.typeck_results().expr_ty(receiver).kind() - && check_lit(left, 1) - { - build_sugg(cx, expr, receiver, &mut applicability); - } + fn build_sugg(&self, cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) { + if is_in_const_context(cx) && !self.msrv.meets(cx, msrvs::CONST_IS_POWER_OF_TWO) { + return; + } - // a & (a - 1) == 0 - if let ExprKind::Binary(op1, left1, right1) = left.kind - && op1.node == BinOpKind::BitAnd - && let ExprKind::Binary(op2, left2, right2) = right1.kind - && op2.node == BinOpKind::Sub - && check_eq_expr(cx, left1, left2) - && let &Uint(_) = cx.typeck_results().expr_ty(left1).kind() - && check_lit(right2, 1) - && check_lit(right, 0) - { - build_sugg(cx, expr, left1, &mut applicability); - } + let mut applicability = Applicability::MachineApplicable; + let snippet = Sugg::hir_with_applicability(cx, receiver, "_", &mut applicability); - // (a - 1) & a == 0; - if let ExprKind::Binary(op1, left1, right1) = left.kind - && op1.node == BinOpKind::BitAnd - && let ExprKind::Binary(op2, left2, right2) = left1.kind - && op2.node == BinOpKind::Sub - && check_eq_expr(cx, right1, left2) - && let &Uint(_) = cx.typeck_results().expr_ty(right1).kind() - && check_lit(right2, 1) - && check_lit(right, 0) - { - build_sugg(cx, expr, right1, &mut applicability); - } + span_lint_and_sugg( + cx, + MANUAL_IS_POWER_OF_TWO, + expr.span, + "manually reimplementing `is_power_of_two`", + "consider using `.is_power_of_two()`", + format!("{}.is_power_of_two()", snippet.maybe_paren()), + applicability, + ); + } +} - // 0 == a & (a - 1); - if let ExprKind::Binary(op1, left1, right1) = right.kind - && op1.node == BinOpKind::BitAnd - && let ExprKind::Binary(op2, left2, right2) = right1.kind - && op2.node == BinOpKind::Sub - && check_eq_expr(cx, left1, left2) - && let &Uint(_) = cx.typeck_results().expr_ty(left1).kind() - && check_lit(right2, 1) - && check_lit(left, 0) +impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if !expr.span.from_expansion() + && let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq) + { + if let Some(a) = count_ones_receiver(cx, lhs) + && is_integer_literal(rhs, 1) { - build_sugg(cx, expr, left1, &mut applicability); - } - - // 0 == (a - 1) & a - if let ExprKind::Binary(op1, left1, right1) = right.kind - && op1.node == BinOpKind::BitAnd - && let ExprKind::Binary(op2, left2, right2) = left1.kind - && op2.node == BinOpKind::Sub - && check_eq_expr(cx, right1, left2) - && let &Uint(_) = cx.typeck_results().expr_ty(right1).kind() - && check_lit(right2, 1) - && check_lit(left, 0) + self.build_sugg(cx, expr, a); + } else if let Some(a) = count_ones_receiver(cx, rhs) + && is_integer_literal(lhs, 1) + { + self.build_sugg(cx, expr, a); + } else if is_integer_literal(rhs, 0) + && let Some(a) = is_and_minus_one(cx, lhs) + { + self.build_sugg(cx, expr, a); + } else if is_integer_literal(lhs, 0) + && let Some(a) = is_and_minus_one(cx, rhs) { - build_sugg(cx, expr, right1, &mut applicability); + self.build_sugg(cx, expr, a); } } } } -fn build_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, applicability: &mut Applicability) { - let snippet = snippet_with_applicability(cx, receiver.span, "..", applicability); - - span_lint_and_sugg( - cx, - MANUAL_IS_POWER_OF_TWO, - expr.span, - "manually reimplementing `is_power_of_two`", - "consider using `.is_power_of_two()`", - format!("{snippet}.is_power_of_two()"), - *applicability, - ); +/// Return the unsigned integer receiver of `.count_ones()` or the argument of +/// `<int-type>::count_ones(…)`. +fn count_ones_receiver<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + let (method, ty, receiver) = if let ExprKind::MethodCall(method_name, receiver, [], _) = expr.kind { + (method_name, cx.typeck_results().expr_ty_adjusted(receiver), receiver) + } else if let ExprKind::Call(func, [arg]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(ty, func_name)) = func.kind + { + (func_name, ty_from_hir_ty(cx, ty), arg) + } else { + return None; + }; + (method.ident.as_str() == "count_ones" && matches!(ty.kind(), ty::Uint(_))).then_some(receiver) } -fn check_lit(expr: &Expr<'_>, expected_num: u128) -> bool { - if let ExprKind::Lit(lit) = expr.kind - && let LitKind::Int(Pu128(num), _) = lit.node - && num == expected_num +/// Return `greater` if `smaller == greater - 1` +fn is_one_less<'tcx>( + cx: &LateContext<'tcx>, + greater: &'tcx Expr<'tcx>, + smaller: &Expr<'tcx>, +) -> Option<&'tcx Expr<'tcx>> { + if let Some((lhs, rhs)) = unexpanded_binop_operands(smaller, BinOpKind::Sub) + && SpanlessEq::new(cx).eq_expr(greater, lhs) + && is_integer_literal(rhs, 1) + && matches!(cx.typeck_results().expr_ty_adjusted(greater).kind(), ty::Uint(_)) { - return true; + Some(greater) + } else { + None } - false } -fn check_eq_expr(cx: &LateContext<'_>, lhs: &Expr<'_>, rhs: &Expr<'_>) -> bool { - SpanlessEq::new(cx).eq_expr(lhs, rhs) +/// Return `v` if `expr` is `v & (v - 1)` or `(v - 1) & v` +fn is_and_minus_one<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + let (lhs, rhs) = unexpanded_binop_operands(expr, BinOpKind::BitAnd)?; + is_one_less(cx, lhs, rhs).or_else(|| is_one_less(cx, rhs, lhs)) +} + +/// Return the operands of the `expr` binary operation if the operator is `op` and none of the +/// operands come from expansion. +fn unexpanded_binop_operands<'hir>(expr: &Expr<'hir>, op: BinOpKind) -> Option<(&'hir Expr<'hir>, &'hir Expr<'hir>)> { + if let ExprKind::Binary(binop, lhs, rhs) = expr.kind + && binop.node == op + && !lhs.span.from_expansion() + && !rhs.span.from_expansion() + { + Some((lhs, rhs)) + } else { + None + } } diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index 8dee29b2a0b..e4ad3953b67 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -34,7 +34,7 @@ declare_clippy_lint! { /// _ = opt.as_slice(); /// _ = opt.as_slice(); /// ``` - #[clippy::version = "1.85.0"] + #[clippy::version = "1.86.0"] pub MANUAL_OPTION_AS_SLICE, complexity, "manual `Option::as_slice`" diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 16dd1ad4e47..98e8b1f5cf9 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -92,10 +92,10 @@ fn check_into_iter( && let [filter_params] = filter_body.params { if match_map_type(cx, left_expr) { - if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind { - if let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) { - make_span_lint_and_sugg(cx, parent_expr_span, sugg); - } + if let hir::PatKind::Tuple([key_pat, value_pat], _) = filter_params.pat.kind + && let Some(sugg) = make_sugg(cx, key_pat, value_pat, left_expr, filter_body) + { + make_span_lint_and_sugg(cx, parent_expr_span, sugg); } // Cannot lint other cases because `retain` requires two parameters } else { @@ -196,22 +196,21 @@ fn check_to_owned( && let filter_body = cx.tcx.hir_body(closure.body) && let [filter_params] = filter_body.params && msrv.meets(cx, msrvs::STRING_RETAIN) + && let hir::PatKind::Ref(pat, _) = filter_params.pat.kind { - if let hir::PatKind::Ref(pat, _) = filter_params.pat.kind { - make_span_lint_and_sugg( - cx, - parent_expr_span, - format!( - "{}.retain(|{}| {})", - snippet(cx, left_expr.span, ".."), - snippet(cx, pat.span, ".."), - snippet(cx, filter_body.value.span, "..") - ), - ); - } - // Be conservative now. Do nothing for the `Binding` case. - // TODO: Ideally, we can rewrite the lambda by stripping one level of reference + make_span_lint_and_sugg( + cx, + parent_expr_span, + format!( + "{}.retain(|{}| {})", + snippet(cx, left_expr.span, ".."), + snippet(cx, pat.span, ".."), + snippet(cx, filter_body.value.span, "..") + ), + ); } + // Be conservative now. Do nothing for the `Binding` case. + // TODO: Ideally, we can rewrite the lambda by stripping one level of reference } fn make_sugg( diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs index 07537fc65c0..06ee00c2cef 100644 --- a/clippy_lints/src/manual_rotate.rs +++ b/clippy_lints/src/manual_rotate.rs @@ -101,7 +101,7 @@ impl LateLintPass<'_> for ManualRotate { (r_shift_dir, r_amount) }; let mut applicability = Applicability::MachineApplicable; - let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_par(); + let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_paren(); span_lint_and_sugg( cx, MANUAL_ROTATE, diff --git a/clippy_lints/src/manual_string_new.rs b/clippy_lints/src/manual_string_new.rs index 5c2a711b5cb..7ca3b712066 100644 --- a/clippy_lints/src/manual_string_new.rs +++ b/clippy_lints/src/manual_string_new.rs @@ -113,15 +113,14 @@ fn parse_call(cx: &LateContext<'_>, span: Span, func: &Expr<'_>, arg: &Expr<'_>) && is_expr_kind_empty_str(&arg.kind) { warn_then_suggest(cx, span); - } else if let QPath::Resolved(_, path) = qpath { + } else if let QPath::Resolved(_, path) = qpath // From::from(...) or TryFrom::try_from(...) - if let [path_seg1, path_seg2] = path.segments - && is_expr_kind_empty_str(&arg.kind) - && ((path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) - || (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from)) - { - warn_then_suggest(cx, span); - } + && let [path_seg1, path_seg2] = path.segments + && is_expr_kind_empty_str(&arg.kind) + && ((path_seg1.ident.name == sym::From && path_seg2.ident.name == sym::from) + || (path_seg1.ident.name == sym::TryFrom && path_seg2.ident.name == sym::try_from)) + { + warn_then_suggest(cx, span); } } } diff --git a/clippy_lints/src/manual_unwrap_or_default.rs b/clippy_lints/src/manual_unwrap_or_default.rs deleted file mode 100644 index 87d2faa225c..00000000000 --- a/clippy_lints/src/manual_unwrap_or_default.rs +++ /dev/null @@ -1,212 +0,0 @@ -use rustc_errors::Applicability; -use rustc_hir::def::Res; -use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::GenericArgKind; -use rustc_session::declare_lint_pass; -use rustc_span::sym; - -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::higher::IfLetOrMatch; -use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{expr_type_is_certain, implements_trait}; -use clippy_utils::{is_default_equivalent, is_in_const_context, path_res, peel_blocks, span_contains_comment}; - -declare_clippy_lint! { - /// ### What it does - /// Checks if a `match` or `if let` expression can be simplified using - /// `.unwrap_or_default()`. - /// - /// ### Why is this bad? - /// It can be done in one call with `.unwrap_or_default()`. - /// - /// ### Example - /// ```no_run - /// let x: Option<String> = Some(String::new()); - /// let y: String = match x { - /// Some(v) => v, - /// None => String::new(), - /// }; - /// - /// let x: Option<Vec<String>> = Some(Vec::new()); - /// let y: Vec<String> = if let Some(v) = x { - /// v - /// } else { - /// Vec::new() - /// }; - /// ``` - /// Use instead: - /// ```no_run - /// let x: Option<String> = Some(String::new()); - /// let y: String = x.unwrap_or_default(); - /// - /// let x: Option<Vec<String>> = Some(Vec::new()); - /// let y: Vec<String> = x.unwrap_or_default(); - /// ``` - #[clippy::version = "1.79.0"] - pub MANUAL_UNWRAP_OR_DEFAULT, - suspicious, - "check if a `match` or `if let` can be simplified with `unwrap_or_default`" -} - -declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]); - -fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<HirId> { - if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind - && let PatKind::Binding(_, pat_id, _, _) = pat.kind - && let Some(def_id) = path.res.opt_def_id() - // Since it comes from a pattern binding, we need to get the parent to actually match - // against it. - && let Some(def_id) = cx.tcx.opt_parent(def_id) - && (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id) - || cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id)) - { - Some(pat_id) - } else { - None - } -} - -fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind - && let Some(def_id) = path.res.opt_def_id() - // Since it comes from a pattern binding, we need to get the parent to actually match - // against it. - && let Some(def_id) = cx.tcx.opt_parent(def_id) - && cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id) - { - Some(arm.body) - } else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.pat.kind - && let Some(def_id) = path.res.opt_def_id() - // Since it comes from a pattern binding, we need to get the parent to actually match - // against it. - && let Some(def_id) = cx.tcx.opt_parent(def_id) - && cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id) - { - Some(arm.body) - } else if let PatKind::Wild = arm.pat.kind { - // We consider that the `Some` check will filter it out if it's not right. - Some(arm.body) - } else { - None - } -} - -fn get_some_and_none_bodies<'tcx>( - cx: &LateContext<'tcx>, - arm1: &'tcx Arm<'tcx>, - arm2: &'tcx Arm<'tcx>, -) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> { - if let Some(binding_id) = get_some(cx, arm1.pat) - && let Some(body_none) = get_none(cx, arm2) - { - Some(((arm1.body, binding_id), body_none)) - } else if let Some(binding_id) = get_some(cx, arm2.pat) - && let Some(body_none) = get_none(cx, arm1) - { - Some(((arm2.body, binding_id), body_none)) - } else { - None - } -} - -#[allow(clippy::needless_pass_by_value)] -fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, expr: &'tcx Expr<'tcx>) { - // Get expr_name ("if let" or "match" depending on kind of expression), the condition, the body for - // the some arm, the body for the none arm and the binding id of the some arm - let (expr_name, condition, body_some, body_none, binding_id) = match if_let_or_match { - IfLetOrMatch::Match(condition, [arm1, arm2], MatchSource::Normal | MatchSource::ForLoopDesugar) - // Make sure there are no guards to keep things simple - if arm1.guard.is_none() - && arm2.guard.is_none() - // Get the some and none bodies and the binding id of the some arm - && let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2) => - { - ("match", condition, body_some, body_none, binding_id) - }, - IfLetOrMatch::IfLet(condition, pat, if_expr, Some(else_expr), _) - if let Some(binding_id) = get_some(cx, pat) => - { - ("if let", condition, if_expr, else_expr, binding_id) - }, - _ => { - // All other cases (match with number of arms != 2, if let without else, etc.) - return; - }, - }; - - // We check if the return type of the expression implements Default. - let expr_type = cx.typeck_results().expr_ty(expr); - if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) - && implements_trait(cx, expr_type, default_trait_id, &[]) - // We check if the initial condition implements Default. - && let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1) - && let GenericArgKind::Type(condition_ty) = condition_ty.unpack() - && implements_trait(cx, condition_ty, default_trait_id, &[]) - // We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`. - && let ExprKind::Path(QPath::Resolved(_, path)) = peel_blocks(body_some).kind - && let Res::Local(local_id) = path.res - && local_id == binding_id - // We now check the `None` arm is calling a method equivalent to `Default::default`. - && let body_none = peel_blocks(body_none) - && is_default_equivalent(cx, body_none) - && let Some(receiver) = Sugg::hir_opt(cx, condition).map(Sugg::maybe_par) - { - // Machine applicable only if there are no comments present - let applicability = if span_contains_comment(cx.sess().source_map(), expr.span) { - Applicability::MaybeIncorrect - } else { - Applicability::MachineApplicable - }; - - // We now check if the condition is a None variant, in which case we need to specify the type - if path_res(cx, condition) - .opt_def_id() - .is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant()) - { - return span_lint_and_sugg( - cx, - MANUAL_UNWRAP_OR_DEFAULT, - expr.span, - format!("{expr_name} can be simplified with `.unwrap_or_default()`"), - "replace it with", - format!("{receiver}::<{expr_type}>.unwrap_or_default()"), - applicability, - ); - } - - // We check if the expression type is still uncertain, in which case we ask the user to specify it - if !expr_type_is_certain(cx, condition) { - return span_lint_and_sugg( - cx, - MANUAL_UNWRAP_OR_DEFAULT, - expr.span, - format!("{expr_name} can be simplified with `.unwrap_or_default()`"), - format!("ascribe the type {expr_type} and replace your expression with"), - format!("{receiver}.unwrap_or_default()"), - Applicability::Unspecified, - ); - } - - span_lint_and_sugg( - cx, - MANUAL_UNWRAP_OR_DEFAULT, - expr.span, - format!("{expr_name} can be simplified with `.unwrap_or_default()`"), - "replace it with", - format!("{receiver}.unwrap_or_default()"), - applicability, - ); - } -} - -impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr) - && !expr.span.from_expansion() - && !is_in_const_context(cx) - { - handle(cx, if_let_or_match, expr); - } - } -} diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 56aead85e7c..b607f8117eb 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -101,10 +101,10 @@ fn is_unit_type(ty: Ty<'_>) -> bool { fn is_unit_function(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { let ty = cx.typeck_results().expr_ty(expr); - if let ty::FnDef(id, _) = *ty.kind() { - if let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().no_bound_vars() { - return is_unit_type(fn_type.output()); - } + if let ty::FnDef(id, _) = *ty.kind() + && let Some(fn_type) = cx.tcx.fn_sig(id).instantiate_identity().no_bound_vars() + { + return is_unit_type(fn_type.output()); } false } diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index 6f446bf9565..5b50efad3e4 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::higher::IfLetOrMatch; use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet; @@ -99,7 +99,7 @@ fn check_arm<'tcx>( } else { String::new() }; - span_lint_and_then(cx, COLLAPSIBLE_MATCH, inner_expr.span, msg, |diag| { + span_lint_hir_and_then(cx, COLLAPSIBLE_MATCH, inner_expr.hir_id, inner_expr.span, msg, |diag| { let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]); help_span.push_span_label(binding_span, "replace this binding"); help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}")); diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index 4cc43e427ec..abf723fa6f4 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -41,10 +41,10 @@ fn get_cond_expr<'tcx>( fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> { // we don't want to use `peel_blocks` here because we don't care if the block is unsafe, it's // checked by `contains_unsafe_block` - if let ExprKind::Block(block, None) = expr.kind { - if block.stmts.is_empty() { - return block.expr; - } + if let ExprKind::Block(block, None) = expr.kind + && block.stmts.is_empty() + { + return block.expr; } None } @@ -61,13 +61,13 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { // } // Returns true if <expr> resolves to `Some(x)`, `false` otherwise fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { - if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) { + if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` - if let ExprKind::Call(callee, [arg]) = inner_expr.kind { - return ctxt == expr.span.ctxt() - && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) - && path_to_local_id(arg, target); - } + && let ExprKind::Call(callee, [arg]) = inner_expr.kind + { + return ctxt == expr.span.ctxt() + && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) + && path_to_local_id(arg, target); } false } diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index 576e42a564c..4959908dad6 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -85,7 +85,7 @@ fn is_variant_or_wildcard(cx: &LateContext<'_>, pat: &Pat<'_>, can_be_wild: bool /// contains `Err(IDENT)`, `None` otherwise. fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &'hir Ident)> { if let PatKind::TupleStruct(qpath, [arg], _) = &pat.kind - && let PatKind::Binding(BindingMode::NONE, _, ident, _) = &arg.kind + && let PatKind::Binding(BindingMode::NONE, _, ident, None) = &arg.kind && let res = cx.qpath_res(qpath, pat.hir_id) && let Res::Def(DefKind::Ctor(..), id) = res && let id @ Some(_) = cx.tcx.opt_parent(id) @@ -132,7 +132,7 @@ fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok } else { Applicability::MachineApplicable }; - let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_par(); + let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_paren(); let sugg = format!("{scrut}.{method}()"); // If the expression being expanded is the `if …` part of an `else if …`, it must be blockified. let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 2bf7ec8ab7d..b64ae0b24d8 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -1,133 +1,219 @@ use clippy_utils::consts::ConstEvalCtxt; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{is_res_lang_ctor, path_to_local_id, peel_blocks, sugg}; +use clippy_utils::source::{SpanRangeExt as _, indent_of, reindent_multiline}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, ResultErr}; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, Pat, PatExpr, PatExprKind, PatKind}; -use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_hir::def::Res; +use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, Pat, PatExpr, PatExprKind, PatKind, QPath}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::{GenericArgKind, Ty}; use rustc_span::sym; -use super::MANUAL_UNWRAP_OR; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::{expr_type_is_certain, get_type_diagnostic_name, implements_trait}; +use clippy_utils::{is_default_equivalent, is_lint_allowed, path_res, peel_blocks, span_contains_comment}; -pub(super) fn check_match<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'tcx>, - scrutinee: &'tcx Expr<'_>, - arms: &'tcx [Arm<'_>], -) { - let ty = cx.typeck_results().expr_ty(scrutinee); - if let Some((or_arm, unwrap_arm)) = applicable_or_arm(cx, arms) { - check_and_lint(cx, expr, unwrap_arm.pat, scrutinee, unwrap_arm.body, or_arm.body, ty); +use super::{MANUAL_UNWRAP_OR, MANUAL_UNWRAP_OR_DEFAULT}; + +fn get_some(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<HirId> { + if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind + && let PatKind::Binding(_, pat_id, _, _) = pat.kind + && let Some(def_id) = path.res.opt_def_id() + // Since it comes from a pattern binding, we need to get the parent to actually match + // against it. + && let Some(def_id) = cx.tcx.opt_parent(def_id) + && let Some(lang_item) = cx.tcx.lang_items().from_def_id(def_id) + && matches!(lang_item, LangItem::OptionSome | LangItem::ResultOk) + { + Some(pat_id) + } else { + None } } -pub(super) fn check_if_let<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - let_pat: &'tcx Pat<'_>, - let_expr: &'tcx Expr<'_>, - then_expr: &'tcx Expr<'_>, - else_expr: &'tcx Expr<'_>, -) { - let ty = cx.typeck_results().expr_ty(let_expr); - let then_ty = cx.typeck_results().expr_ty(then_expr); - // The signature is `fn unwrap_or<T>(self: Option<T>, default: T) -> T`. - // When `expr_adjustments(then_expr).is_empty()`, `T` should equate to `default`'s type. - // Otherwise, type error will occur. - if cx.typeck_results().expr_adjustments(then_expr).is_empty() - && let rustc_middle::ty::Adt(_did, args) = ty.kind() - && let Some(some_ty) = args.first().and_then(|arg| arg.as_type()) - && some_ty != then_ty +fn get_none<'tcx>(cx: &LateContext<'_>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind + && let Some(def_id) = path.res.opt_def_id() + // Since it comes from a pattern binding, we need to get the parent to actually match + // against it. + && let Some(def_id) = cx.tcx.opt_parent(def_id) + && cx.tcx.lang_items().get(LangItem::OptionNone) == Some(def_id) { - return; + Some(arm.body) + } else if let PatKind::TupleStruct(QPath::Resolved(_, path), _, _)= arm.pat.kind + && let Some(def_id) = path.res.opt_def_id() + // Since it comes from a pattern binding, we need to get the parent to actually match + // against it. + && let Some(def_id) = cx.tcx.opt_parent(def_id) + && cx.tcx.lang_items().get(LangItem::ResultErr) == Some(def_id) + { + Some(arm.body) + } else if let PatKind::Wild = arm.pat.kind { + // We consider that the `Some` check will filter it out if it's not right. + Some(arm.body) + } else { + None } - check_and_lint(cx, expr, let_pat, let_expr, then_expr, peel_blocks(else_expr), ty); } -fn check_and_lint<'tcx>( +fn get_some_and_none_bodies<'tcx>( cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - let_pat: &'tcx Pat<'_>, - let_expr: &'tcx Expr<'_>, - then_expr: &'tcx Expr<'_>, - else_expr: &'tcx Expr<'_>, - ty: Ty<'tcx>, + arm1: &'tcx Arm<'tcx>, + arm2: &'tcx Arm<'tcx>, +) -> Option<((&'tcx Expr<'tcx>, HirId), &'tcx Expr<'tcx>)> { + if let Some(binding_id) = get_some(cx, arm1.pat) + && let Some(body_none) = get_none(cx, arm2) + { + Some(((arm1.body, binding_id), body_none)) + } else if let Some(binding_id) = get_some(cx, arm2.pat) + && let Some(body_none) = get_none(cx, arm1) + { + Some(((arm2.body, binding_id), body_none)) + } else { + None + } +} + +fn handle( + cx: &LateContext<'_>, + expr: &Expr<'_>, + expr_name: &'static str, + condition: &Expr<'_>, + body_some: &Expr<'_>, + body_none: &Expr<'_>, + binding_id: HirId, ) { - if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = let_pat.kind - && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id) - && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) - && (cx.tcx.lang_items().option_some_variant() == Some(variant_id) - || cx.tcx.lang_items().result_ok_variant() == Some(variant_id)) - && let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind - && path_to_local_id(peel_blocks(then_expr), binding_hir_id) - && cx.typeck_results().expr_adjustments(then_expr).is_empty() - && let Some(ty_name) = find_type_name(cx, ty) - && let Some(or_body_snippet) = else_expr.span.get_source_text(cx) - && let Some(indent) = indent_of(cx, expr.span) - && ConstEvalCtxt::new(cx).eval_simple(else_expr).is_some() + // Only deal with situations where both alternatives return the same non-adjusted type. + if cx.typeck_results().expr_ty(body_some) != cx.typeck_results().expr_ty(body_none) { + return; + } + + let expr_type = cx.typeck_results().expr_ty(expr); + // We check that the `Some(x) => x` doesn't do anything apart "returning" the value in `Some`. + if let ExprKind::Path(QPath::Resolved(_, path)) = peel_blocks(body_some).kind + && let Res::Local(local_id) = path.res + && local_id == binding_id { - lint(cx, expr, let_expr, ty_name, &or_body_snippet, indent); + // Machine applicable only if there are no comments present + let mut applicability = if span_contains_comment(cx.sess().source_map(), expr.span) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + let receiver = Sugg::hir_with_applicability(cx, condition, "_", &mut applicability).maybe_paren(); + + // We now check the `None` arm is calling a method equivalent to `Default::default`. + if !is_lint_allowed(cx, MANUAL_UNWRAP_OR_DEFAULT, expr.hir_id) + // We check if the return type of the expression implements Default. + && let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) + && implements_trait(cx, expr_type, default_trait_id, &[]) + // We check if the initial condition implements Default. + && let Some(condition_ty) = cx.typeck_results().expr_ty(condition).walk().nth(1) + && let GenericArgKind::Type(condition_ty) = condition_ty.unpack() + && implements_trait(cx, condition_ty, default_trait_id, &[]) + && is_default_equivalent(cx, peel_blocks(body_none)) + { + // We now check if the condition is a None variant, in which case we need to specify the type + if path_res(cx, condition) + .opt_def_id() + .is_some_and(|id| Some(cx.tcx.parent(id)) == cx.tcx.lang_items().option_none_variant()) + { + return span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR_DEFAULT, + expr.span, + format!("{expr_name} can be simplified with `.unwrap_or_default()`"), + "replace it with", + format!("{receiver}::<{expr_type}>.unwrap_or_default()"), + applicability, + ); + } + + // We check if the expression type is still uncertain, in which case we ask the user to specify it + if !expr_type_is_certain(cx, condition) { + return span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR_DEFAULT, + expr.span, + format!("{expr_name} can be simplified with `.unwrap_or_default()`"), + format!("ascribe the type {expr_type} and replace your expression with"), + format!("{receiver}.unwrap_or_default()"), + Applicability::Unspecified, + ); + } + + span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR_DEFAULT, + expr.span, + format!("{expr_name} can be simplified with `.unwrap_or_default()`"), + "replace it with", + format!("{receiver}.unwrap_or_default()"), + applicability, + ); + } else if let Some(ty_name) = find_type_name(cx, cx.typeck_results().expr_ty(condition)) + && cx.typeck_results().expr_adjustments(body_some).is_empty() + && let Some(or_body_snippet) = peel_blocks(body_none).span.get_source_text(cx) + && let Some(indent) = indent_of(cx, expr.span) + && ConstEvalCtxt::new(cx).eval_simple(body_none).is_some() + { + let reindented_or_body = reindent_multiline(&or_body_snippet, true, Some(indent)); + let mut app = Applicability::MachineApplicable; + let suggestion = Sugg::hir_with_context(cx, condition, expr.span.ctxt(), "..", &mut app).maybe_paren(); + span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR, + expr.span, + format!("this pattern reimplements `{ty_name}::unwrap_or`"), + "replace with", + format!("{suggestion}.unwrap_or({reindented_or_body})",), + app, + ); + } } } fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static str> { - if is_type_diagnostic_item(cx, ty, sym::Option) { - Some("Option") - } else if is_type_diagnostic_item(cx, ty, sym::Result) { - Some("Result") - } else { - None + match get_type_diagnostic_name(cx, ty)? { + sym::Option => Some("Option"), + sym::Result => Some("Result"), + _ => None, } } -fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<(&'a Arm<'a>, &'a Arm<'a>)> { - if arms.len() == 2 - && arms.iter().all(|arm| arm.guard.is_none()) - && let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind { - PatKind::Expr(PatExpr { - hir_id, - kind: PatExprKind::Path(qpath), - .. - }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), - PatKind::TupleStruct(ref qpath, [pat], _) => { - matches!(pat.kind, PatKind::Wild) - && is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr) - }, - _ => false, - }) - && let unwrap_arm = &arms[1 - idx] - && !contains_return_break_continue_macro(or_arm.body) +pub fn check_match<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + scrutinee: &'tcx Expr<'tcx>, + arms: &'tcx [Arm<'tcx>], +) { + if let [arm1, arm2] = arms + // Make sure there are no guards to keep things simple + && arm1.guard.is_none() + && arm2.guard.is_none() + // Get the some and none bodies and the binding id of the some arm + && let Some(((body_some, binding_id), body_none)) = get_some_and_none_bodies(cx, arm1, arm2) { - Some((or_arm, unwrap_arm)) - } else { - None + handle(cx, expr, "match", scrutinee, body_some, body_none, binding_id); } } -fn lint<'tcx>( +pub fn check_if_let<'tcx>( cx: &LateContext<'tcx>, - expr: &Expr<'tcx>, - scrutinee: &'tcx Expr<'_>, - ty_name: &str, - or_body_snippet: &str, - indent: usize, + expr: &'tcx Expr<'tcx>, + pat: &'tcx Pat<'tcx>, + scrutinee: &'tcx Expr<'tcx>, + then_expr: &'tcx Expr<'tcx>, + else_expr: &'tcx Expr<'tcx>, ) { - let reindented_or_body = reindent_multiline(or_body_snippet, true, Some(indent)); - - let mut app = Applicability::MachineApplicable; - let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par(); - span_lint_and_sugg( - cx, - MANUAL_UNWRAP_OR, - expr.span, - format!("this pattern reimplements `{ty_name}::unwrap_or`"), - "replace with", - format!("{suggestion}.unwrap_or({reindented_or_body})",), - app, - ); + if let Some(binding_id) = get_some(cx, pat) { + handle( + cx, + expr, + "if let", + scrutinee, + peel_blocks(then_expr), + peel_blocks(else_expr), + binding_id, + ); + } } diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index d29d1ea3e96..f14b69d91ce 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -76,17 +76,18 @@ where && first_attrs.is_empty() && iter.all(|arm| find_bool_lit(&arm.2.kind).is_some_and(|b| b == b0) && arm.3.is_none() && arm.0.is_empty()) { - if let Some(last_pat) = last_pat_opt { - if !is_wild(last_pat) { - return false; - } + if let Some(last_pat) = last_pat_opt + && !is_wild(last_pat) + { + return false; } for arm in iter_without_last.clone() { - if let Some(pat) = arm.1 { - if !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) && is_some(pat.kind) { - return false; - } + if let Some(pat) = arm.1 + && !is_lint_allowed(cx, REDUNDANT_PATTERN_MATCHING, pat.hir_id) + && is_some(pat.kind) + { + return false; } } @@ -113,10 +114,10 @@ where // strip potential borrows (#6503), but only if the type is a reference let mut ex_new = ex; - if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind { - if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { - ex_new = ex_inner; - } + if let ExprKind::AddrOf(BorrowKind::Ref, .., ex_inner) = ex.kind + && let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() + { + ex_new = ex_inner; } span_lint_and_sugg( cx, diff --git a/clippy_lints/src/matches/match_on_vec_items.rs b/clippy_lints/src/matches/match_on_vec_items.rs deleted file mode 100644 index dd71560e169..00000000000 --- a/clippy_lints/src/matches/match_on_vec_items.rs +++ /dev/null @@ -1,50 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem}; -use rustc_lint::LateContext; -use rustc_span::sym; - -use super::MATCH_ON_VEC_ITEMS; - -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>) { - if let Some(idx_expr) = is_vec_indexing(cx, scrutinee) - && let ExprKind::Index(vec, idx, _) = idx_expr.kind - { - // FIXME: could be improved to suggest surrounding every pattern with Some(_), - // but only when `or_patterns` are stabilized. - span_lint_and_sugg( - cx, - MATCH_ON_VEC_ITEMS, - scrutinee.span, - "indexing into a vector may panic", - "try", - format!("{}.get({})", snippet(cx, vec.span, ".."), snippet(cx, idx.span, "..")), - Applicability::MaybeIncorrect, - ); - } -} - -fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Index(array, index, _) = expr.kind - && is_vector(cx, array) - && !is_full_range(cx, index) - { - return Some(expr); - } - - None -} - -fn is_vector(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let ty = cx.typeck_results().expr_ty(expr); - let ty = ty.peel_refs(); - is_type_diagnostic_item(cx, ty, sym::Vec) -} - -fn is_full_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let ty = cx.typeck_results().expr_ty(expr); - let ty = ty.peel_refs(); - is_type_lang_item(cx, ty, LangItem::RangeFull) -} diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 864923b2773..adda3586990 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::macros::HirNode; -use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_applicability}; +use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_context}; use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}; @@ -24,16 +24,10 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e let bind_names = arms[0].pat.span; let match_body = peel_blocks(arms[0].body); let mut app = Applicability::MaybeIncorrect; - let mut snippet_body = snippet_block_with_context( - cx, - match_body.span, - arms[0].span.ctxt(), - "..", - Some(expr.span), - &mut app, - ) - .0 - .to_string(); + let ctxt = expr.span.ctxt(); + let mut snippet_body = snippet_block_with_context(cx, match_body.span, ctxt, "..", Some(expr.span), &mut app) + .0 + .to_string(); // Do we need to add ';' to suggestion ? if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) @@ -77,10 +71,10 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e span, format!( "let {} = {};\n{}let {} = {snippet_body};", - snippet_with_applicability(cx, bind_names, "..", &mut app), - snippet_with_applicability(cx, matched_vars, "..", &mut app), + snippet_with_context(cx, bind_names, ctxt, "..", &mut app).0, + snippet_with_context(cx, matched_vars, ctxt, "..", &mut app).0, " ".repeat(indent_of(cx, expr.span).unwrap_or(0)), - snippet_with_applicability(cx, pat_span, "..", &mut app) + snippet_with_context(cx, pat_span, ctxt, "..", &mut app).0 ), ), None => { @@ -178,24 +172,24 @@ fn sugg_with_curlies<'a>( let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if let Some(parent_expr) = get_parent_expr(cx, match_expr) { - if let ExprKind::Closure { .. } = parent_expr.kind { - cbrace_end = format!("\n{indent}}}"); - // Fix body indent due to the closure - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{indent}"); - } + if let Some(parent_expr) = get_parent_expr(cx, match_expr) + && let ExprKind::Closure { .. } = parent_expr.kind + { + cbrace_end = format!("\n{indent}}}"); + // Fix body indent due to the closure + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{indent}"); } // If the parent is already an arm, and the body is another match statement, // we need curly braces around suggestion - if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) { - if let ExprKind::Match(..) = arm.body.kind { - cbrace_end = format!("\n{indent}}}"); - // Fix body indent due to the match - indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); - cbrace_start = format!("{{\n{indent}"); - } + if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) + && let ExprKind::Match(..) = arm.body.kind + { + cbrace_end = format!("\n{indent}}}"); + // Fix body indent due to the match + indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); + cbrace_start = format!("{{\n{indent}"); } let assignment_str = assignment.map_or_else(String::new, |span| { @@ -204,14 +198,17 @@ fn sugg_with_curlies<'a>( s }); + let ctxt = match_expr.span.ctxt(); let scrutinee = if needs_var_binding { format!( "let {} = {}", - snippet_with_applicability(cx, bind_names, "..", applicability), - snippet_with_applicability(cx, matched_vars, "..", applicability) + snippet_with_context(cx, bind_names, ctxt, "..", applicability).0, + snippet_with_context(cx, matched_vars, ctxt, "..", applicability).0 ) } else { - snippet_with_applicability(cx, matched_vars, "..", applicability).to_string() + snippet_with_context(cx, matched_vars, ctxt, "..", applicability) + .0 + .to_string() }; format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}") diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index df1b83cbb51..65b93a095b9 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -26,10 +26,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm && let ty::Str = ty.kind() { 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()); - } + if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) + && 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()); } } } diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 11b588b3355..24b4a675800 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -80,18 +80,20 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { path }, PatKind::TupleStruct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p)) { - missing_variants.retain(|e| e.ctor_def_id() != Some(id)); - } + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() + && arm.guard.is_none() + && patterns.iter().all(|p| !is_refutable(cx, p)) + { + missing_variants.retain(|e| e.ctor_def_id() != Some(id)); } path }, PatKind::Struct(path, patterns, ..) => { - if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() { - if arm.guard.is_none() && patterns.iter().all(|p| !is_refutable(cx, p.pat)) { - missing_variants.retain(|e| e.def_id != id); - } + if let Some(id) = cx.qpath_res(path, pat.hir_id).opt_def_id() + && arm.guard.is_none() + && patterns.iter().all(|p| !is_refutable(cx, p.pat)) + { + missing_variants.retain(|e| e.def_id != id); } path }, diff --git a/clippy_lints/src/matches/match_wild_err_arm.rs b/clippy_lints/src/matches/match_wild_err_arm.rs index d0d2025878e..8ce8453360f 100644 --- a/clippy_lints/src/matches/match_wild_err_arm.rs +++ b/clippy_lints/src/matches/match_wild_err_arm.rs @@ -26,11 +26,12 @@ pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'tcx>, arms: &[Arm<' if !matching_wild { // Looking for unused bindings (i.e.: `_e`) for pat in inner { - if let PatKind::Binding(_, id, ident, None) = pat.kind { - if ident.as_str().starts_with('_') && !is_local_used(cx, arm.body, id) { - ident_bind_name = ident.name; - matching_wild = true; - } + if let PatKind::Binding(_, id, ident, None) = pat.kind + && ident.as_str().starts_with('_') + && !is_local_used(cx, arm.body, id) + { + ident_bind_name = ident.name; + matching_wild = true; } } } diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 2b9173e6f41..c6ebd6144c7 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -8,7 +8,6 @@ mod manual_utils; mod match_as_ref; mod match_bool; mod match_like_matches; -mod match_on_vec_items; mod match_ref_pats; mod match_same_arms; mod match_single_binding; @@ -724,38 +723,39 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `match vec[idx]` or `match vec[n..m]`. + /// Checks if a `match` or `if let` expression can be simplified using + /// `.unwrap_or_default()`. /// /// ### Why is this bad? - /// This can panic at runtime. + /// It can be done in one call with `.unwrap_or_default()`. /// /// ### Example - /// ```rust, no_run - /// let arr = vec![0, 1, 2, 3]; - /// let idx = 1; + /// ```no_run + /// let x: Option<String> = Some(String::new()); + /// let y: String = match x { + /// Some(v) => v, + /// None => String::new(), + /// }; /// - /// match arr[idx] { - /// 0 => println!("{}", 0), - /// 1 => println!("{}", 3), - /// _ => {}, - /// } + /// let x: Option<Vec<String>> = Some(Vec::new()); + /// let y: Vec<String> = if let Some(v) = x { + /// v + /// } else { + /// Vec::new() + /// }; /// ``` - /// /// Use instead: - /// ```rust, no_run - /// let arr = vec![0, 1, 2, 3]; - /// let idx = 1; + /// ```no_run + /// let x: Option<String> = Some(String::new()); + /// let y: String = x.unwrap_or_default(); /// - /// match arr.get(idx) { - /// Some(0) => println!("{}", 0), - /// Some(1) => println!("{}", 3), - /// _ => {}, - /// } + /// let x: Option<Vec<String>> = Some(Vec::new()); + /// let y: Vec<String> = x.unwrap_or_default(); /// ``` - #[clippy::version = "1.45.0"] - pub MATCH_ON_VEC_ITEMS, - pedantic, - "matching on vector elements can panic" + #[clippy::version = "1.79.0"] + pub MANUAL_UNWRAP_OR_DEFAULT, + suspicious, + "check if a `match` or `if let` can be simplified with `unwrap_or_default`" } declare_clippy_lint! { @@ -1040,7 +1040,7 @@ impl_lint_pass!(Matches => [ NEEDLESS_MATCH, COLLAPSIBLE_MATCH, MANUAL_UNWRAP_OR, - MATCH_ON_VEC_ITEMS, + MANUAL_UNWRAP_OR_DEFAULT, MATCH_STR_CASE_MISMATCH, SIGNIFICANT_DROP_IN_SCRUTINEE, TRY_ERR, @@ -1118,7 +1118,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_wild_enum::check(cx, ex, arms); match_as_ref::check(cx, ex, arms, expr); needless_match::check_match(cx, ex, arms, expr); - match_on_vec_items::check(cx, ex); match_str_case_mismatch::check(cx, ex, arms); redundant_guards::check(cx, arms, self.msrv); diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index 7e65d586110..6c5d7cab203 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -67,10 +67,10 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>]) for arm in arms { let arm_expr = peel_blocks_with_stmt(arm.body); - if let Some(guard_expr) = &arm.guard { - if guard_expr.can_have_side_effects() { - return false; - } + if let Some(guard_expr) = &arm.guard + && guard_expr.can_have_side_effects() + { + return false; } if let PatKind::Wild = arm.pat.kind { diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs index 4184f8b9e6e..d3136c89178 100644 --- a/clippy_lints/src/matches/overlapping_arms.rs +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -11,17 +11,17 @@ use super::MATCH_OVERLAPPING_ARM; pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { if arms.len() >= 2 && cx.typeck_results().expr_ty(ex).is_integral() { let ranges = all_ranges(cx, arms, cx.typeck_results().expr_ty(ex)); - if !ranges.is_empty() { - if let Some((start, end)) = overlapping(&ranges) { - span_lint_and_note( - cx, - MATCH_OVERLAPPING_ARM, - start.span, - "some ranges overlap", - Some(end.span), - "overlaps with this", - ); - } + if !ranges.is_empty() + && let Some((start, end)) = overlapping(&ranges) + { + span_lint_and_note( + cx, + MATCH_OVERLAPPING_ARM, + start.span, + "some ranges overlap", + Some(end.span), + "overlaps with this", + ); } } } diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 722ea7042dd..db20be40f27 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -4,7 +4,7 @@ use clippy_utils::source::walk_span_to_context; use clippy_utils::sugg::{Sugg, make_unop}; use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop}; use clippy_utils::visitors::{any_temporaries_need_ordered_drop, for_each_expr_without_closures}; -use clippy_utils::{higher, is_expn_of, is_trait_method}; +use clippy_utils::{higher, is_expn_of, is_trait_method, sym}; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; @@ -12,7 +12,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatExpr, PatExprKind, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol}; use std::fmt::Write; use std::ops::ControlFlow; @@ -138,9 +138,9 @@ fn find_method_and_type<'tcx>( Some(("is_some()", op_ty)) } else if Some(id) == lang_items.poll_ready_variant() { Some(("is_ready()", op_ty)) - } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V4))) { + } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym::V4)) { Some(("is_ipv4()", op_ty)) - } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym!(V6))) { + } else if is_pat_variant(cx, check_pat, qpath, Item::Diag(sym::IpAddr, sym::V6)) { Some(("is_ipv6()", op_ty)) } else { None @@ -255,7 +255,7 @@ fn find_method_sugg_for_if_let<'tcx>( }; let sugg = Sugg::hir_with_context(cx, result_expr, ctxt, "_", &mut app) - .maybe_par() + .maybe_paren() .to_string(); diag.span_suggestion(span, "try", format!("{keyword} {sugg}.{good_method}"), app); @@ -279,7 +279,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op _ => op, }; let mut app = Applicability::MachineApplicable; - let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_par(); + let receiver_sugg = Sugg::hir_with_applicability(cx, result_expr, "_", &mut app).maybe_paren(); let mut sugg = format!("{receiver_sugg}.{good_method}"); if let Some(guard) = maybe_guard { @@ -303,7 +303,7 @@ pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, op } let guard = Sugg::hir(cx, guard, ".."); - let _ = write!(sugg, " && {}", guard.maybe_par()); + let _ = write!(sugg, " && {}", guard.maybe_paren()); } span_lint_and_sugg( @@ -345,8 +345,8 @@ fn found_good_method<'tcx>( arms, path_left, path_right, - Item::Diag(sym::IpAddr, sym!(V4)), - Item::Diag(sym::IpAddr, sym!(V6)), + Item::Diag(sym::IpAddr, sym::V4), + Item::Diag(sym::IpAddr, sym::V6), "is_ipv4()", "is_ipv6()", ) @@ -437,8 +437,8 @@ fn get_good_method<'tcx>( "None" => (Item::Lang(OptionNone), "is_none()", "is_some()"), "Ready" => (Item::Lang(PollReady), "is_ready()", "is_pending()"), "Pending" => (Item::Lang(PollPending), "is_pending()", "is_ready()"), - "V4" => (Item::Diag(sym::IpAddr, sym!(V4)), "is_ipv4()", "is_ipv6()"), - "V6" => (Item::Diag(sym::IpAddr, sym!(V6)), "is_ipv6()", "is_ipv4()"), + "V4" => (Item::Diag(sym::IpAddr, sym::V4), "is_ipv4()", "is_ipv6()"), + "V6" => (Item::Diag(sym::IpAddr, sym::V6), "is_ipv6()", "is_ipv4()"), _ => return None, }; return find_good_method_for_matches_macro( diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 37bac561a6e..d7dc7604088 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -182,17 +182,16 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { - if let Some(adt) = ty.ty_adt_def() { - if get_attr( + if let Some(adt) = ty.ty_adt_def() + && get_attr( self.cx.sess(), self.cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop", ) .count() > 0 - { - return true; - } + { + return true; } if !self.seen_types.insert(ty) { diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 836c46240ce..08c0caa4266 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -1,5 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::{SpanRangeExt, expr_block, snippet, snippet_block_with_context}; +use clippy_utils::source::{ + SpanRangeExt, expr_block, snippet, snippet_block_with_context, snippet_with_applicability, snippet_with_context, +}; use clippy_utils::ty::implements_trait; use clippy_utils::{ is_lint_allowed, is_unit_expr, peel_blocks, peel_hir_pat_refs, peel_middle_ty_refs, peel_n_hir_expr_refs, @@ -34,8 +36,7 @@ fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool { #[rustfmt::skip] pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], expr: &'tcx Expr<'_>, contains_comments: bool) { if let [arm1, arm2] = arms - && arm1.guard.is_none() - && arm2.guard.is_none() + && !arms.iter().any(|arm| arm.guard.is_some() || arm.pat.span.from_expansion()) && !expr.span.from_expansion() // don't lint for or patterns for now, this makes // the lint noisy in unnecessary situations @@ -106,7 +107,7 @@ fn report_single_pattern( format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app)) }); - if snippet(cx, ex.span, "..") == snippet(cx, arm.pat.span, "..") { + if ex.span.eq_ctxt(expr.span) && snippet(cx, ex.span, "..") == snippet(cx, arm.pat.span, "..") { let msg = "this pattern is irrefutable, `match` is useless"; let (sugg, help) = if is_unit_expr(arm.body) { (String::new(), "`match` expression can be removed") @@ -163,10 +164,10 @@ fn report_single_pattern( let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`"; let sugg = format!( "if {} == {}{} {}{els_str}", - snippet(cx, ex.span, ".."), + snippet_with_context(cx, ex.span, ctxt, "..", &mut app).0, // PartialEq for different reference counts may not exist. "&".repeat(ref_count_diff), - snippet(cx, arm.pat.span, ".."), + snippet_with_applicability(cx, arm.pat.span, "..", &mut app), expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app), ); (msg, sugg) @@ -174,8 +175,8 @@ fn report_single_pattern( let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; let sugg = format!( "if let {} = {} {}{els_str}", - snippet(cx, arm.pat.span, ".."), - snippet(cx, ex.span, ".."), + snippet_with_applicability(cx, arm.pat.span, "..", &mut app), + snippet_with_context(cx, ex.span, ctxt, "..", &mut app).0, expr_block(cx, arm.body, ctxt, "..", Some(expr.span), &mut app), ); (msg, sugg) diff --git a/clippy_lints/src/matches/wild_in_or_pats.rs b/clippy_lints/src/matches/wild_in_or_pats.rs index b75d1ab9a7a..43102d78bfe 100644 --- a/clippy_lints/src/matches/wild_in_or_pats.rs +++ b/clippy_lints/src/matches/wild_in_or_pats.rs @@ -15,18 +15,18 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arms: &[Arm<'_>]) { return; } for arm in arms { - if let PatKind::Or(fields) = arm.pat.kind { + if let PatKind::Or(fields) = arm.pat.kind // look for multiple fields in this arm that contains at least one Wild pattern - if fields.len() > 1 && fields.iter().any(is_wild) { - span_lint_and_help( - cx, - WILDCARD_IN_OR_PATTERNS, - arm.pat.span, - "wildcard pattern covers any other pattern as it will match anyway", - None, - "consider handling `_` separately", - ); - } + && fields.len() > 1 && fields.iter().any(is_wild) + { + span_lint_and_help( + cx, + WILDCARD_IN_OR_PATTERNS, + arm.pat.span, + "wildcard pattern covers any other pattern as it will match anyway", + None, + "consider handling `_` separately", + ); } } } diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index a0919947b3f..a54d835b538 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -145,7 +145,7 @@ fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &E "consider `Option::take()` instead", format!( "{}.take()", - Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_par() + Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_paren() ), applicability, ); @@ -178,7 +178,7 @@ fn check_replace_option_with_some( "consider `Option::replace()` instead", format!( "{}.replace({})", - Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "_", &mut applicability).maybe_par(), + Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "_", &mut applicability).maybe_paren(), snippet_with_applicability(cx, src_arg.span, "_", &mut applicability) ), applicability, @@ -304,14 +304,12 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace { && let ExprKind::Path(ref func_qpath) = func.kind && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::mem_replace, def_id) - { // Check that second argument is `Option::None` - if !check_replace_option_with_none(cx, src, dest, expr.span) - && !check_replace_option_with_some(cx, src, dest, expr.span, self.msrv) - && !check_replace_with_default(cx, src, dest, expr, self.msrv) - { - check_replace_with_uninit(cx, src, dest, expr.span); - } + && !check_replace_option_with_none(cx, src, dest, expr.span) + && !check_replace_option_with_some(cx, src, dest, expr.span, self.msrv) + && !check_replace_with_default(cx, src, dest, expr, self.msrv) + { + check_replace_with_uninit(cx, src, dest, expr.span); } } } diff --git a/clippy_lints/src/methods/bind_instead_of_map.rs b/clippy_lints/src/methods/bind_instead_of_map.rs index 1e9b29f567f..f8520c23ea5 100644 --- a/clippy_lints/src/methods/bind_instead_of_map.rs +++ b/clippy_lints/src/methods/bind_instead_of_map.rs @@ -192,10 +192,10 @@ impl BindInsteadOfMap { } fn is_variant(&self, cx: &LateContext<'_>, res: Res) -> bool { - if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res { - if let Some(variant_id) = cx.tcx.lang_items().get(self.variant_lang_item) { - return cx.tcx.parent(id) == variant_id; - } + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res + && let Some(variant_id) = cx.tcx.lang_items().get(self.variant_lang_item) + { + return cx.tcx.parent(id) == variant_id; } false } diff --git a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs index 18568e3661f..d07870d4951 100644 --- a/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/methods/case_sensitive_file_extension_comparisons.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; @@ -16,14 +17,15 @@ pub(super) fn check<'tcx>( call_span: Span, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>, + msrv: Msrv, ) { - if let ExprKind::MethodCall(path_segment, ..) = recv.kind { - if matches!( + if let ExprKind::MethodCall(path_segment, ..) = recv.kind + && matches!( path_segment.ident.name.as_str(), "to_lowercase" | "to_uppercase" | "to_ascii_lowercase" | "to_ascii_uppercase" - ) { - return; - } + ) + { + return; } if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) @@ -58,11 +60,15 @@ pub(super) fn check<'tcx>( let suggestion_source = reindent_multiline( &format!( - "std::path::Path::new({}) + "std::path::Path::new({recv_source}) .extension() - .map_or(false, |ext| ext.eq_ignore_ascii_case(\"{}\"))", - recv_source, - ext_str.strip_prefix('.').unwrap() + .{}|ext| ext.eq_ignore_ascii_case(\"{}\"))", + if msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) { + "is_some_and(" + } else { + "map_or(false, " + }, + ext_str.strip_prefix('.').unwrap(), ), true, Some(indent_of(cx, call_span).unwrap_or(0) + 4), diff --git a/clippy_lints/src/methods/clone_on_copy.rs b/clippy_lints/src/methods/clone_on_copy.rs index 1ee27d90d05..2ecf3eb8979 100644 --- a/clippy_lints/src/methods/clone_on_copy.rs +++ b/clippy_lints/src/methods/clone_on_copy.rs @@ -40,10 +40,10 @@ pub(super) fn check( .map_or_else(|| cx.typeck_results().expr_ty(arg), |a| a.target); let ty = cx.typeck_results().expr_ty(expr); - if let ty::Ref(_, inner, _) = arg_ty.kind() { - if let ty::Ref(..) = inner.kind() { - return; // don't report clone_on_copy - } + if let ty::Ref(_, inner, _) = arg_ty.kind() + && let ty::Ref(..) = inner.kind() + { + return; // don't report clone_on_copy } if is_copy(cx, ty) { diff --git a/clippy_lints/src/methods/double_ended_iterator_last.rs b/clippy_lints/src/methods/double_ended_iterator_last.rs index b5adc69e9a7..e666f31217c 100644 --- a/clippy_lints/src/methods/double_ended_iterator_last.rs +++ b/clippy_lints/src/methods/double_ended_iterator_last.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::implements_trait; +use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait}; use clippy_utils::{is_mutable, is_trait_method, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{Expr, Node, PatKind}; @@ -27,10 +27,15 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp && let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name().as_str() == "last") // if the resolved method is the same as the provided definition && fn_def.def_id() == last_def.def_id + && let self_ty = cx.typeck_results().expr_ty(self_expr) + && !has_non_owning_mutable_access(cx, self_ty) { let mut sugg = vec![(call_span, String::from("next_back()"))]; let mut dont_apply = false; + // if `self_expr` is a reference, it is mutable because it is used for `.last()` + // TODO: Change this to lint only when the referred iterator is not used later. If it is used later, + // changing to `next_back()` may change its behavior. if !(is_mutable(cx, self_expr) || self_type.is_ref()) { if let Some(hir_id) = path_to_local(self_expr) && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index daa6e0e7f94..f5688e370a4 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -54,10 +54,11 @@ pub(super) fn check<'tcx>( if is_type_lang_item(cx, arg_ty, hir::LangItem::String) { return false; } - if let ty::Ref(_, ty, ..) = arg_ty.kind() { - if ty.is_str() && can_be_static_str(cx, arg) { - return false; - } + if let ty::Ref(_, ty, ..) = arg_ty.kind() + && ty.is_str() + && can_be_static_str(cx, arg) + { + return false; } true } diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index ae300cd5fe5..da123f13d46 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{is_panic, matching_root_macro_call, root_macro_call}; use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{SpanlessEq, higher, is_trait_method, path_to_local_id, peel_blocks}; +use clippy_utils::{SpanlessEq, higher, is_trait_method, path_to_local_id, peel_blocks, sym}; use hir::{Body, HirId, MatchSource, Pat}; use rustc_errors::Applicability; use rustc_hir as hir; @@ -11,7 +11,7 @@ use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::adjustment::Adjust; use rustc_span::Span; -use rustc_span::symbol::{Ident, Symbol, sym}; +use rustc_span::symbol::{Ident, Symbol}; use super::{MANUAL_FILTER_MAP, MANUAL_FIND_MAP, OPTION_FILTER_MAP, RESULT_FILTER_MAP}; @@ -43,10 +43,10 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool } fn is_option_filter_map(cx: &LateContext<'_>, filter_arg: &Expr<'_>, map_arg: &Expr<'_>) -> bool { - is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) + is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym::is_some) } fn is_ok_filter_map(cx: &LateContext<'_>, filter_arg: &Expr<'_>, map_arg: &Expr<'_>) -> bool { - is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_ok)) + is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym::is_ok) } #[derive(Debug, Copy, Clone)] @@ -429,16 +429,15 @@ fn is_find_or_filter<'a>( } fn acceptable_methods(method: &PathSegment<'_>) -> bool { - let methods: [Symbol; 8] = [ - sym::clone, - sym::as_ref, - sym!(copied), - sym!(cloned), - sym!(as_deref), - sym!(as_mut), - sym!(as_deref_mut), - sym!(to_owned), - ]; - - methods.contains(&method.ident.name) + matches!( + method.ident.name, + sym::clone + | sym::as_ref + | sym::copied + | sym::cloned + | sym::as_deref + | sym::as_mut + | sym::as_deref_mut + | sym::to_owned + ) } diff --git a/clippy_lints/src/methods/filter_map_bool_then.rs b/clippy_lints/src/methods/filter_map_bool_then.rs index f7e116c5310..965993808f6 100644 --- a/clippy_lints/src/methods/filter_map_bool_then.rs +++ b/clippy_lints/src/methods/filter_map_bool_then.rs @@ -1,10 +1,14 @@ use super::FILTER_MAP_BOOL_THEN; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; -use clippy_utils::{is_from_proc_macro, is_trait_method, peel_blocks}; +use clippy_utils::{ + CaptureKind, can_move_expr_to_closure, contains_return, is_from_proc_macro, is_trait_method, peel_blocks, +}; +use rustc_ast::Mutability; +use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, HirId, Param, Pat}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::Binder; use rustc_middle::ty::adjustment::Adjust; @@ -44,17 +48,69 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, arg: & && let Some(filter) = recv.span.get_source_text(cx) && let Some(map) = then_body.span.get_source_text(cx) { - span_lint_and_sugg( + span_lint_and_then( cx, FILTER_MAP_BOOL_THEN, call_span, "usage of `bool::then` in `filter_map`", - "use `filter` then `map` instead", - format!( - "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", - derefs = "*".repeat(needed_derefs) - ), - Applicability::MachineApplicable, + |diag| { + if can_filter_and_then_move_to_closure(cx, ¶m, recv, then_body) { + diag.span_suggestion( + call_span, + "use `filter` then `map` instead", + format!( + "filter(|&{param_snippet}| {derefs}{filter}).map(|{param_snippet}| {map})", + derefs = "*".repeat(needed_derefs) + ), + Applicability::MachineApplicable, + ); + } else { + diag.help("consider using `filter` then `map` instead"); + } + }, ); } } + +/// Returns a set of all bindings found in the given pattern. +fn find_bindings_from_pat(pat: &Pat<'_>) -> FxHashSet<HirId> { + let mut bindings = FxHashSet::default(); + pat.walk(|p| { + if let rustc_hir::PatKind::Binding(_, hir_id, _, _) = p.kind { + bindings.insert(hir_id); + } + true + }); + bindings +} + +/// Returns true if we can take a closure parameter and have it in both the `filter` function and +/// the`map` function. This is not the case if: +/// +/// - The `filter` would contain an early return, +/// - `filter` and `then` contain captures, and any of those are &mut +fn can_filter_and_then_move_to_closure<'tcx>( + cx: &LateContext<'tcx>, + param: &Param<'tcx>, + filter: &'tcx Expr<'tcx>, + then: &'tcx Expr<'tcx>, +) -> bool { + if contains_return(filter) { + return false; + } + + let Some(filter_captures) = can_move_expr_to_closure(cx, filter) else { + return true; + }; + let Some(then_captures) = can_move_expr_to_closure(cx, then) else { + return true; + }; + + let param_bindings = find_bindings_from_pat(param.pat); + filter_captures.iter().all(|(hir_id, filter_cap)| { + param_bindings.contains(hir_id) + || !then_captures + .get(hir_id) + .is_some_and(|then_cap| matches!(*filter_cap | *then_cap, CaptureKind::Ref(Mutability::Mut))) + }) +} diff --git a/clippy_lints/src/methods/from_iter_instead_of_collect.rs b/clippy_lints/src/methods/from_iter_instead_of_collect.rs index f4840785584..045363058d1 100644 --- a/clippy_lints/src/methods/from_iter_instead_of_collect.rs +++ b/clippy_lints/src/methods/from_iter_instead_of_collect.rs @@ -1,25 +1,31 @@ +use std::fmt::Write as _; + use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::{is_path_diagnostic_item, sugg}; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::def::Res; +use rustc_hir::{self as hir, Expr, ExprKind, GenericArg, QPath, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::GenericParamDefKind; use rustc_span::sym; use super::FROM_ITER_INSTEAD_OF_COLLECT; -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>], func: &Expr<'_>) { if is_path_diagnostic_item(cx, func, sym::from_iter_fn) - && let ty = cx.typeck_results().expr_ty(expr) && let arg_ty = cx.typeck_results().expr_ty(&args[0]) && let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) && implements_trait(cx, arg_ty, iter_id, &[]) { - // `expr` implements `FromIterator` trait - let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par(); - let turbofish = extract_turbofish(cx, expr, ty); + let mut app = Applicability::MaybeIncorrect; + let turbofish = match func.kind { + ExprKind::Path(QPath::TypeRelative(hir_ty, _)) => build_full_type(cx, hir_ty, &mut app), + ExprKind::Path(QPath::Resolved(Some(self_ty), _)) => build_full_type(cx, self_ty, &mut app), + _ => return, + }; + let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_paren(); let sugg = format!("{iter_expr}.collect::<{turbofish}>()"); span_lint_and_sugg( cx, @@ -28,54 +34,47 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp "usage of `FromIterator::from_iter`", "use `.collect()` instead of `::from_iter()`", sugg, - Applicability::MaybeIncorrect, + app, ); } } -fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'_>) -> String { - fn strip_angle_brackets(s: &str) -> Option<&str> { - s.strip_prefix('<')?.strip_suffix('>') - } - - let call_site = expr.span.source_callsite(); - if let Some(snippet) = call_site.get_source_text(cx) - && let snippet_split = snippet.split("::").collect::<Vec<_>>() - && let Some((_, elements)) = snippet_split.split_last() +/// Build a type which can be used in a turbofish syntax from `hir_ty`, either by copying the +/// existing generic arguments with the exception of elided lifetimes, or by inserting placeholders +/// for types and consts without default values. +fn build_full_type(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, app: &mut Applicability) -> String { + if let TyKind::Path(ty_qpath) = hir_ty.kind + && let QPath::Resolved(None, ty_path) = &ty_qpath + && let Res::Def(_, ty_did) = ty_path.res { - if let [type_specifier, _] = snippet_split.as_slice() - && let Some(type_specifier) = strip_angle_brackets(type_specifier) - && let Some((type_specifier, ..)) = type_specifier.split_once(" as ") - { - type_specifier.to_string() + let mut ty_str = itertools::join(ty_path.segments.iter().map(|s| s.ident), "::"); + let mut first = true; + let mut append = |arg: &str| { + write!(&mut ty_str, "{}{arg}", [", ", "<"][usize::from(first)]).unwrap(); + first = false; + }; + if let Some(args) = ty_path.segments.last().and_then(|segment| segment.args) { + args.args + .iter() + .filter(|arg| !matches!(arg, GenericArg::Lifetime(lt) if lt.is_elided())) + .for_each(|arg| append(&snippet_with_applicability(cx, arg.span().source_callsite(), "_", app))); } else { - // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`) - if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) { - // remove the type specifier from the path elements - let without_ts = elements - .iter() - .filter_map(|e| { - if e == type_specifier { - None - } else { - Some((*e).to_string()) - } - }) - .collect::<Vec<_>>(); - // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`) - format!("{}{type_specifier}", without_ts.join("::")) - } else { - // type is not explicitly specified so wildcards are needed - // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>` - let ty_str = ty.to_string(); - let start = ty_str.find('<').unwrap_or(0); - let end = ty_str.find('>').unwrap_or(ty_str.len()); - let nb_wildcard = ty_str[start..end].split(',').count(); - let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1)); - format!("{}<{wildcards}>", elements.join("::")) - } + cx.tcx + .generics_of(ty_did) + .own_params + .iter() + .filter(|param| { + matches!( + param.kind, + GenericParamDefKind::Type { has_default: false, .. } + | GenericParamDefKind::Const { has_default: false, .. } + ) + }) + .for_each(|_| append("_")); } + ty_str.push_str([">", ""][usize::from(first)]); + ty_str } else { - ty.to_string() + snippet_with_applicability(cx, hir_ty.span.source_callsite(), "_", app).into() } } diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index 4c81b22861b..545bef1a4c5 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -14,15 +14,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_ if expr.span.in_external_macro(cx.sess().source_map()) || !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; - } - } + if let Some(parent) = get_parent_expr(cx, expr) + && let Some(parent) = get_parent_expr(cx, parent) + && 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) { diff --git a/clippy_lints/src/methods/iter_cloned_collect.rs b/clippy_lints/src/methods/iter_cloned_collect.rs index 49de83885a1..17cc07b91c5 100644 --- a/clippy_lints/src/methods/iter_cloned_collect.rs +++ b/clippy_lints/src/methods/iter_cloned_collect.rs @@ -1,16 +1,22 @@ use crate::methods::utils::derefs_to_slice; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{get_iterator_item_ty, is_type_diagnostic_item}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; +use rustc_middle::ty; use rustc_span::sym; use super::ITER_CLONED_COLLECT; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, method_name: &str, expr: &hir::Expr<'_>, recv: &'tcx hir::Expr<'_>) { - if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) + let expr_ty = cx.typeck_results().expr_ty(expr); + if is_type_diagnostic_item(cx, expr_ty, sym::Vec) && let Some(slice) = derefs_to_slice(cx, recv, cx.typeck_results().expr_ty(recv)) + && let ty::Adt(_, args) = expr_ty.kind() + && let Some(iter_item_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty(recv)) + && let ty::Ref(_, iter_item_ty, _) = iter_item_ty.kind() + && *iter_item_ty == args.type_at(0) && let Some(to_replace) = expr.span.trim_start(slice.span.source_callsite()) { span_lint_and_sugg( diff --git a/clippy_lints/src/methods/iter_filter.rs b/clippy_lints/src/methods/iter_filter.rs index bafabec7e06..adeff375c8a 100644 --- a/clippy_lints/src/methods/iter_filter.rs +++ b/clippy_lints/src/methods/iter_filter.rs @@ -6,12 +6,12 @@ use super::{ITER_FILTER_IS_OK, ITER_FILTER_IS_SOME}; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline}; -use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment}; +use clippy_utils::{get_parent_expr, is_trait_method, peel_blocks, span_contains_comment, sym}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::QPath; use rustc_span::Span; -use rustc_span::symbol::{Ident, Symbol, sym}; +use rustc_span::symbol::{Ident, Symbol}; /// /// Returns true if the expression is a method call to `method_name` @@ -154,7 +154,7 @@ fn expression_type( if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Option) && let opt_ty = cx.tcx.type_of(opt_defid).skip_binder() && iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def() - && is_method(cx, filter_arg, sym::Option, sym!(is_some), &[]) + && is_method(cx, filter_arg, sym::Option, sym::is_some, &[]) { return Some(FilterType::IsSome); } @@ -162,7 +162,7 @@ fn expression_type( if let Some(opt_defid) = cx.tcx.get_diagnostic_item(sym::Result) && let opt_ty = cx.tcx.type_of(opt_defid).skip_binder() && iter_item_ty.ty_adt_def() == opt_ty.ty_adt_def() - && is_method(cx, filter_arg, sym::Result, sym!(is_ok), &[]) + && is_method(cx, filter_arg, sym::Result, sym::is_ok, &[]) { return Some(FilterType::IsOk); } diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index 94415fc9106..3ac9299ba91 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -37,7 +37,7 @@ pub(super) fn check<'tcx>( (PatKind::Binding(ann, _, key, _), value) if pat_is_wild(cx, value, m_arg) => ("key", ann, key), _ => return, } - && let ty = cx.typeck_results().expr_ty(recv) + && let ty = cx.typeck_results().expr_ty_adjusted(recv).peel_refs() && (is_type_diagnostic_item(cx, ty, sym::HashMap) || is_type_diagnostic_item(cx, ty, sym::BTreeMap)) { let mut applicability = rustc_errors::Applicability::MachineApplicable; diff --git a/clippy_lints/src/methods/iterator_step_by_zero.rs b/clippy_lints/src/methods/iterator_step_by_zero.rs index 9b358235a40..90d5d9df55e 100644 --- a/clippy_lints/src/methods/iterator_step_by_zero.rs +++ b/clippy_lints/src/methods/iterator_step_by_zero.rs @@ -8,14 +8,14 @@ use rustc_span::sym; use super::ITERATOR_STEP_BY_ZERO; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>, arg: &'tcx hir::Expr<'_>) { - if is_trait_method(cx, expr, sym::Iterator) { - if let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg) { - span_lint( - cx, - ITERATOR_STEP_BY_ZERO, - expr.span, - "`Iterator::step_by(0)` will panic at runtime", - ); - } + if is_trait_method(cx, expr, sym::Iterator) + && let Some(Constant::Int(0)) = ConstEvalCtxt::new(cx).eval(arg) + { + span_lint( + cx, + ITERATOR_STEP_BY_ZERO, + expr.span, + "`Iterator::step_by(0)` will panic at runtime", + ); } } diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index 13918ed11b8..18978a1d2bc 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -106,15 +106,15 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> { }; let check_lit = |expr: &hir::Expr<'_>, check_min: bool| { - if let hir::ExprKind::Lit(lit) = &expr.kind { - if let ast::LitKind::Int(value, _) = lit.node { - if value == maxval { - return Some(MinMax::Max); - } - - if check_min && value == minval { - return Some(MinMax::Min); - } + if let hir::ExprKind::Lit(lit) = &expr.kind + && let ast::LitKind::Int(value, _) = lit.node + { + if value == maxval { + return Some(MinMax::Max); + } + + if check_min && value == minval { + return Some(MinMax::Min); } } @@ -125,10 +125,10 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> { return r; } - if ty.is_signed() { - if let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind { - return check_lit(val, true); - } + if ty.is_signed() + && let hir::ExprKind::Unary(hir::UnOp::Neg, val) = &expr.kind + { + return check_lit(val, true); } None diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index 098721dc046..8167e4f9605 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints/src/methods/manual_str_repeat.rs @@ -77,7 +77,7 @@ pub(super) fn check( s @ Cow::Borrowed(_) => s, }, RepeatKind::String => Sugg::hir_with_context(cx, repeat_arg, ctxt, "..", &mut app) - .maybe_par() + .maybe_paren() .to_string() .into(), }; diff --git a/clippy_lints/src/methods/map_clone.rs b/clippy_lints/src/methods/map_clone.rs index 128b3695f48..333a33f7527 100644 --- a/clippy_lints/src/methods/map_clone.rs +++ b/clippy_lints/src/methods/map_clone.rs @@ -51,19 +51,19 @@ pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_ let closure_expr = peel_blocks(closure_body.value); match closure_body.params[0].pat.kind { hir::PatKind::Ref(inner, Mutability::Not) => { - if let hir::PatKind::Binding(hir::BindingMode::NONE, .., name, None) = inner.kind { - if ident_eq(name, closure_expr) { - lint_explicit_closure(cx, e.span, recv.span, true, msrv); - } + if let hir::PatKind::Binding(hir::BindingMode::NONE, .., name, None) = inner.kind + && ident_eq(name, closure_expr) + { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); } }, hir::PatKind::Binding(hir::BindingMode::NONE, .., name, None) => { match closure_expr.kind { hir::ExprKind::Unary(hir::UnOp::Deref, inner) => { - if ident_eq(name, inner) { - if let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() { - lint_explicit_closure(cx, e.span, recv.span, true, msrv); - } + if ident_eq(name, inner) + && let ty::Ref(.., Mutability::Not) = cx.typeck_results().expr_ty(inner).kind() + { + lint_explicit_closure(cx, e.span, recv.span, true, msrv); } }, hir::ExprKind::MethodCall(method, obj, [], _) => { @@ -114,19 +114,17 @@ fn handle_path( ) { if let Some(path_def_id) = cx.qpath_res(qpath, arg.hir_id).opt_def_id() && cx.tcx.lang_items().get(LangItem::CloneFn) == Some(path_def_id) - { // The `copied` and `cloned` methods are only available on `&T` and `&mut T` in `Option` // and `Result`. - if let ty::Adt(_, args) = cx.typeck_results().expr_ty(recv).kind() - && let args = args.as_slice() - && let Some(ty) = args.iter().find_map(|generic_arg| generic_arg.as_type()) - && let ty::Ref(_, ty, Mutability::Not) = ty.kind() - && let ty::FnDef(_, lst) = cx.typeck_results().expr_ty(arg).kind() - && lst.iter().all(|l| l.as_type() == Some(*ty)) - && !should_call_clone_as_function(cx, *ty) - { - lint_path(cx, e.span, recv.span, is_copy(cx, ty.peel_refs())); - } + && let ty::Adt(_, args) = cx.typeck_results().expr_ty(recv).kind() + && let args = args.as_slice() + && let Some(ty) = args.iter().find_map(|generic_arg| generic_arg.as_type()) + && let ty::Ref(_, ty, Mutability::Not) = ty.kind() + && let ty::FnDef(_, lst) = cx.typeck_results().expr_ty(arg).kind() + && lst.iter().all(|l| l.as_type() == Some(*ty)) + && !should_call_clone_as_function(cx, *ty) + { + lint_path(cx, e.span, recv.span, is_copy(cx, ty.peel_refs())); } } 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 index 6cf0936c598..a2a522a6068 100644 --- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -41,7 +41,7 @@ fn extract_count_with_applicability( return Some(format!("{count}")); } let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) - .maybe_par() + .maybe_paren() .into_string(); if lower_bound == 0 { if range.limits == RangeLimits::Closed { diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 1d9296016e2..ad374dee516 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -114,6 +114,7 @@ mod suspicious_command_arg_space; mod suspicious_map; mod suspicious_splitn; mod suspicious_to_owned; +mod swap_with_temporary; mod type_id_on_box; mod unbuffered_bytes; mod uninit_assumed_init; @@ -478,9 +479,6 @@ declare_clippy_lint! { /// Because you usually call `expect()` on the `Result` /// directly to get a better error message. /// - /// ### Known problems - /// The error type needs to implement `Debug` - /// /// ### Example /// ```no_run /// # let x = Ok::<_, ()>(()); @@ -2429,7 +2427,7 @@ declare_clippy_lint! { /// /// ### Limitations /// This lint currently only looks for usages of - /// `.then_some(..).unwrap_or(..)` and `.then(..).unwrap_or(..)`, but will be expanded + /// `.{then, then_some}(..).{unwrap_or, unwrap_or_else, unwrap_or_default}(..)`, but will be expanded /// to account for similar patterns. /// /// ### Example @@ -4286,7 +4284,7 @@ declare_clippy_lint! { /// ```no_run /// let last_arg = "echo hello world".split(' ').next_back(); /// ``` - #[clippy::version = "1.85.0"] + #[clippy::version = "1.86.0"] pub DOUBLE_ENDED_ITERATOR_LAST, perf, "using `Iterator::last` on a `DoubleEndedIterator`" @@ -4478,12 +4476,59 @@ declare_clippy_lint! { /// ```no_run /// let _ = std::io::Error::other("bad".to_string()); /// ``` - #[clippy::version = "1.86.0"] + #[clippy::version = "1.87.0"] pub IO_OTHER_ERROR, style, "calling `std::io::Error::new(std::io::ErrorKind::Other, _)`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `std::mem::swap` with temporary values. + /// + /// ### Why is this bad? + /// Storing a new value in place of a temporary value which will + /// be dropped right after the `swap` is an inefficient way of performing + /// an assignment. The same result can be achieved by using a regular + /// assignment. + /// + /// ### Examples + /// ```no_run + /// fn replace_string(s: &mut String) { + /// std::mem::swap(s, &mut String::from("replaced")); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn replace_string(s: &mut String) { + /// *s = String::from("replaced"); + /// } + /// ``` + /// + /// Also, swapping two temporary values has no effect, as they will + /// both be dropped right after swapping them. This is likely an indication + /// of a bug. For example, the following code swaps the references to + /// the last element of the vectors, instead of swapping the elements + /// themselves: + /// + /// ```no_run + /// fn bug(v1: &mut [i32], v2: &mut [i32]) { + /// // Incorrect: swapping temporary references (`&mut &mut` passed to swap) + /// std::mem::swap(&mut v1.last_mut().unwrap(), &mut v2.last_mut().unwrap()); + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn correct(v1: &mut [i32], v2: &mut [i32]) { + /// std::mem::swap(v1.last_mut().unwrap(), v2.last_mut().unwrap()); + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub SWAP_WITH_TEMPORARY, + complexity, + "detect swap with a temporary value" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4661,17 +4706,19 @@ impl_lint_pass!(Methods => [ UNBUFFERED_BYTES, MANUAL_CONTAINS, IO_OTHER_ERROR, + SWAP_WITH_TEMPORARY, ]); /// Extracts a method call name, args, and `Span` of the method name. pub fn method_call<'tcx>( recv: &'tcx Expr<'tcx>, ) -> Option<(&'tcx str, &'tcx Expr<'tcx>, &'tcx [Expr<'tcx>], Span, Span)> { - if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind { - if !args.iter().any(|e| e.span.from_expansion()) && !receiver.span.from_expansion() { - let name = path.ident.name.as_str(); - return Some((name, receiver, args, path.ident.span, call_span)); - } + if let ExprKind::MethodCall(path, receiver, args, call_span) = recv.kind + && !args.iter().any(|e| e.span.from_expansion()) + && !receiver.span.from_expansion() + { + let name = path.ident.name.as_str(); + return Some((name, receiver, args, path.ident.span, call_span)); } None } @@ -4691,6 +4738,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { manual_c_str_literals::check(cx, expr, func, args, self.msrv); useless_nonzero_new_unchecked::check(cx, expr, func, args, self.msrv); io_other_error::check(cx, expr, func, args, self.msrv); + swap_with_temporary::check(cx, expr, func, args); }, ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; @@ -4992,7 +5040,7 @@ impl Methods { }, ("ends_with", [arg]) => { if let ExprKind::MethodCall(.., span) = expr.kind { - case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg); + case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg, self.msrv); } path_ends_with_ext::check(cx, recv, arg, expr, self.msrv, &self.allowed_dotfiles); }, @@ -5421,15 +5469,21 @@ impl Methods { option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv); }, Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg, then_method, "unwrap_or"); + obfuscated_if_else::check(cx, expr, t_recv, t_arg, Some(u_arg), then_method, "unwrap_or"); }, _ => {}, } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, ("unwrap_or_default", []) => { - if let Some(("map", m_recv, [arg], span, _)) = method_call(recv) { - manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv); + match method_call(recv) { + Some(("map", m_recv, [arg], span, _)) => { + manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv); + }, + Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => { + obfuscated_if_else::check(cx, expr, t_recv, t_arg, None, then_method, "unwrap_or_default"); + }, + _ => {}, } unnecessary_literal_unwrap::check(cx, expr, recv, name, args); }, @@ -5441,7 +5495,15 @@ impl Methods { Some(("map", recv, [map_arg], _, _)) if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {}, Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg, then_method, "unwrap_or_else"); + obfuscated_if_else::check( + cx, + expr, + t_recv, + t_arg, + Some(u_arg), + then_method, + "unwrap_or_else", + ); }, _ => { unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or"); diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index e4a29b6560e..6efaba525e3 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -4,7 +4,9 @@ use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{get_type_diagnostic_name, make_normalized_projection, make_projection}; +use clippy_utils::ty::{ + get_type_diagnostic_name, has_non_owning_mutable_access, make_normalized_projection, make_projection, +}; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, fn_def_id, get_enclosing_block, higher, is_trait_method, path_to_local, path_to_local_id, @@ -23,6 +25,7 @@ use rustc_span::{Span, sym}; const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed"; +#[expect(clippy::too_many_lines)] pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, name_span: Span, @@ -30,6 +33,11 @@ pub(super) fn check<'tcx>( iter_expr: &'tcx Expr<'tcx>, call_span: Span, ) { + let iter_ty = cx.typeck_results().expr_ty(iter_expr); + if has_non_owning_mutable_access(cx, iter_ty) { + return; // don't lint if the iterator has side effects + } + match cx.tcx.parent_hir_node(collect_expr.hir_id) { Node::Expr(parent) => { check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); @@ -377,20 +385,20 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { return; } - if let Some(hir_id) = path_to_local(recv) { - if let Some(index) = self.hir_id_uses_map.remove(&hir_id) { - if self - .illegal_mutable_capture_ids - .intersection(&self.current_mutably_captured_ids) - .next() - .is_none() - { - if let Some(hir_id) = self.current_statement_hir_id { - self.hir_id_uses_map.insert(hir_id, index); - } - } else { - self.uses[index] = None; + if let Some(hir_id) = path_to_local(recv) + && let Some(index) = self.hir_id_uses_map.remove(&hir_id) + { + if self + .illegal_mutable_capture_ids + .intersection(&self.current_mutably_captured_ids) + .next() + .is_none() + { + if let Some(hir_id) = self.current_statement_hir_id { + self.hir_id_uses_map.insert(hir_id, index); } + } else { + self.uses[index] = None; } } } diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs index 88b9c69f6f9..cd1b97f3c51 100644 --- a/clippy_lints/src/methods/needless_option_take.rs +++ b/clippy_lints/src/methods/needless_option_take.rs @@ -9,26 +9,27 @@ use super::NEEDLESS_OPTION_TAKE; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>) { // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place - if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) { - if let Some(function_name) = source_of_temporary_value(recv) { - span_lint_and_then( - cx, - NEEDLESS_OPTION_TAKE, - expr.span, - "called `Option::take()` on a temporary value", - |diag| { - diag.note(format!( - "`{function_name}` creates a temporary value, so calling take() has no effect" - )); - diag.span_suggestion( - expr.span.with_lo(recv.span.hi()), - "remove", - "", - Applicability::MachineApplicable, - ); - }, - ); - } + if !recv.is_syntactic_place_expr() + && is_expr_option(cx, recv) + && let Some(function_name) = source_of_temporary_value(recv) + { + span_lint_and_then( + cx, + NEEDLESS_OPTION_TAKE, + expr.span, + "called `Option::take()` on a temporary value", + |diag| { + diag.note(format!( + "`{function_name}` creates a temporary value, so calling take() has no effect" + )); + diag.span_suggestion( + expr.span.with_lo(recv.span.hi()), + "remove", + "", + Applicability::MachineApplicable, + ); + }, + ); } } @@ -44,10 +45,10 @@ fn is_expr_option(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn source_of_temporary_value<'a>(expr: &'a Expr<'_>) -> Option<&'a str> { match expr.peel_borrows().kind { ExprKind::Call(function, _) => { - if let ExprKind::Path(QPath::Resolved(_, func_path)) = function.kind { - if !func_path.segments.is_empty() { - return Some(func_path.segments[0].ident.name.as_str()); - } + if let ExprKind::Path(QPath::Resolved(_, func_path)) = function.kind + && !func_path.segments.is_empty() + { + return Some(func_path.segments[0].ident.name.as_str()); } if let ExprKind::Path(QPath::TypeRelative(_, func_path_segment)) = function.kind { return Some(func_path_segment.ident.name.as_str()); diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints/src/methods/obfuscated_if_else.rs index 9a5ffdeaf4e..1cc56de4876 100644 --- a/clippy_lints/src/methods/obfuscated_if_else.rs +++ b/clippy_lints/src/methods/obfuscated_if_else.rs @@ -14,14 +14,17 @@ pub(super) fn check<'tcx>( expr: &'tcx hir::Expr<'_>, then_recv: &'tcx hir::Expr<'_>, then_arg: &'tcx hir::Expr<'_>, - unwrap_arg: &'tcx hir::Expr<'_>, + unwrap_arg: Option<&'tcx hir::Expr<'_>>, then_method_name: &str, unwrap_method_name: &str, ) { let recv_ty = cx.typeck_results().expr_ty(then_recv); if recv_ty.is_bool() { - let mut applicability = if switch_to_eager_eval(cx, then_arg) && switch_to_eager_eval(cx, unwrap_arg) { + let then_eager = switch_to_eager_eval(cx, then_arg); + let unwrap_eager = unwrap_arg.is_none_or(|arg| switch_to_eager_eval(cx, arg)); + + let mut applicability = if then_eager && unwrap_eager { Applicability::MachineApplicable } else { Applicability::MaybeIncorrect @@ -36,16 +39,17 @@ pub(super) fn check<'tcx>( _ => return, }; - // FIXME: Add `unwrap_or_else` symbol + // FIXME: Add `unwrap_or_else` and `unwrap_or_default` symbol let els = match unwrap_method_name { - "unwrap_or" => snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability), - "unwrap_or_else" if let ExprKind::Closure(closure) = unwrap_arg.kind => { + "unwrap_or" => snippet_with_applicability(cx, unwrap_arg.unwrap().span, "..", &mut applicability), + "unwrap_or_else" if let ExprKind::Closure(closure) = unwrap_arg.unwrap().kind => { let body = cx.tcx.hir_body(closure.body); snippet_with_applicability(cx, body.value.span, "..", &mut applicability) }, - "unwrap_or_else" if let ExprKind::Path(_) = unwrap_arg.kind => { - snippet_with_applicability(cx, unwrap_arg.span, "_", &mut applicability) + "()" + "unwrap_or_else" if let ExprKind::Path(_) = unwrap_arg.unwrap().kind => { + snippet_with_applicability(cx, unwrap_arg.unwrap().span, "_", &mut applicability) + "()" }, + "unwrap_or_default" => "Default::default()".into(), _ => return, }; diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 6eeeea5d77c..b78b082e460 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -6,13 +6,13 @@ use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; use clippy_utils::visitors::for_each_expr; use clippy_utils::{ - contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment, peel_blocks, + contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment, peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; -use rustc_span::symbol::{self, Symbol, sym}; +use rustc_span::symbol::{self, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; @@ -66,8 +66,8 @@ pub(super) fn check<'tcx>( }; let sugg = match (name, call_expr.is_some()) { - ("unwrap_or", true) | ("unwrap_or_else", false) => sym!(unwrap_or_default), - ("or_insert", true) | ("or_insert_with", false) => sym!(or_default), + ("unwrap_or", true) | ("unwrap_or_else", false) => sym::unwrap_or_default, + ("or_insert", true) | ("or_insert_with", false) => sym::or_default, _ => return false, }; @@ -78,8 +78,7 @@ pub(super) fn check<'tcx>( .iter() .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) .find_map(|assoc| { - if assoc.is_method() - && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 + if assoc.is_method() && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 { Some(assoc.def_id) } else { diff --git a/clippy_lints/src/methods/seek_from_current.rs b/clippy_lints/src/methods/seek_from_current.rs index d318462e584..8b51268da46 100644 --- a/clippy_lints/src/methods/seek_from_current.rs +++ b/clippy_lints/src/methods/seek_from_current.rs @@ -3,33 +3,33 @@ use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_span::sym; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_enum_variant_ctor; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; +use clippy_utils::{is_enum_variant_ctor, sym}; use super::SEEK_FROM_CURRENT; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &'tcx Expr<'_>, arg: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(recv); - if let Some(def_id) = cx.tcx.get_diagnostic_item(sym::IoSeek) { - if implements_trait(cx, ty, def_id, &[]) && arg_is_seek_from_current(cx, arg) { - let mut applicability = Applicability::MachineApplicable; - let snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability); + if let Some(def_id) = cx.tcx.get_diagnostic_item(sym::IoSeek) + && implements_trait(cx, ty, def_id, &[]) + && arg_is_seek_from_current(cx, arg) + { + let mut applicability = Applicability::MachineApplicable; + let snip = snippet_with_applicability(cx, recv.span, "..", &mut applicability); - span_lint_and_sugg( - cx, - SEEK_FROM_CURRENT, - expr.span, - "using `SeekFrom::Current` to start from current position", - "replace with", - format!("{snip}.stream_position()"), - applicability, - ); - } + span_lint_and_sugg( + cx, + SEEK_FROM_CURRENT, + expr.span, + "using `SeekFrom::Current` to start from current position", + "replace with", + format!("{snip}.stream_position()"), + applicability, + ); } } @@ -37,14 +37,12 @@ fn arg_is_seek_from_current<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) if let ExprKind::Call(f, [arg]) = expr.kind && let ExprKind::Path(ref path) = f.kind && let Some(ctor_call_id) = cx.qpath_res(path, f.hir_id).opt_def_id() - && is_enum_variant_ctor(cx, sym::SeekFrom, sym!(Current), ctor_call_id) - { + && is_enum_variant_ctor(cx, sym::SeekFrom, sym::Current, ctor_call_id) // check if argument of `SeekFrom::Current` is `0` - if let ExprKind::Lit(lit) = arg.kind - && let LitKind::Int(Pu128(0), LitIntType::Unsuffixed) = lit.node - { - return true; - } + && let ExprKind::Lit(lit) = arg.kind + && let LitKind::Int(Pu128(0), LitIntType::Unsuffixed) = lit.node + { + return true; } false diff --git a/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs b/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs index 7b1dd9e58c5..b8405a78f23 100644 --- a/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs +++ b/clippy_lints/src/methods/seek_to_start_instead_of_rewind.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_enum_variant_ctor, is_expr_used_or_unified}; +use clippy_utils::{is_enum_variant_ctor, is_expr_used_or_unified, sym}; use rustc_ast::ast::{LitIntType, LitKind}; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_span::{Span, sym}; +use rustc_span::Span; use super::SEEK_TO_START_INSTEAD_OF_REWIND; @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( && let ExprKind::Call(func, [arg]) = arg.kind && let ExprKind::Path(ref path) = func.kind && let Some(ctor_call_id) = cx.qpath_res(path, func.hir_id).opt_def_id() - && is_enum_variant_ctor(cx, sym::SeekFrom, sym!(Start), ctor_call_id) + && is_enum_variant_ctor(cx, sym::SeekFrom, sym::Start, ctor_call_id) && let ExprKind::Lit(lit) = arg.kind && let LitKind::Int(Pu128(0), LitIntType::Unsuffixed) = lit.node { diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 4ccefb7ec9d..d183457da25 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -238,15 +238,14 @@ fn indirect_usage<'tcx>( unwrap_kind: Some(unwrap_kind), .. } = iter_usage + && parent_id == local_hir_id { - if parent_id == local_hir_id { - return Some(IndirectUsage { - name: ident.name, - span: stmt.span, - init_expr, - unwrap_kind, - }); - } + return Some(IndirectUsage { + name: ident.name, + span: stmt.span, + init_expr, + unwrap_kind, + }); } } diff --git a/clippy_lints/src/methods/suspicious_map.rs b/clippy_lints/src/methods/suspicious_map.rs index 1bd48525f12..788014d9bb6 100644 --- a/clippy_lints/src/methods/suspicious_map.rs +++ b/clippy_lints/src/methods/suspicious_map.rs @@ -13,11 +13,11 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, count_recv: &hir::Expr< && let closure_body = cx.tcx.hir_body(closure.body) && !cx.typeck_results().expr_ty(closure_body.value).is_unit() { - if let Some(map_mutated_vars) = mutated_variables(closure_body.value, cx) { + if let Some(map_mutated_vars) = mutated_variables(closure_body.value, cx) // A variable is used mutably inside of the closure. Suppress the lint. - if !map_mutated_vars.is_empty() { - return; - } + && !map_mutated_vars.is_empty() + { + return; } span_lint_and_help( cx, diff --git a/clippy_lints/src/methods/swap_with_temporary.rs b/clippy_lints/src/methods/swap_with_temporary.rs new file mode 100644 index 00000000000..de729fb343a --- /dev/null +++ b/clippy_lints/src/methods/swap_with_temporary.rs @@ -0,0 +1,125 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::sugg::Sugg; +use rustc_ast::BorrowKind; +use rustc_errors::{Applicability, Diag}; +use rustc_hir::{Expr, ExprKind, Node, QPath}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::SWAP_WITH_TEMPORARY; + +const MSG_TEMPORARY: &str = "this expression returns a temporary value"; +const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value"; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { + if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind + && let Some(func_def_id) = func_path.res.opt_def_id() + && cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id) + { + match (ArgKind::new(&args[0]), ArgKind::new(&args[1])) { + (ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => { + emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp); + }, + (ArgKind::RefMutToTemp(left_temp), right) => emit_lint_assign(cx, expr, &right, &args[0], left_temp), + (left, ArgKind::RefMutToTemp(right_temp)) => emit_lint_assign(cx, expr, &left, &args[1], right_temp), + _ => {}, + } + } +} + +enum ArgKind<'tcx> { + // Mutable reference to a place, coming from a macro + RefMutToPlaceAsMacro(&'tcx Expr<'tcx>), + // Place behind a mutable reference + RefMutToPlace(&'tcx Expr<'tcx>), + // Temporary value behind a mutable reference + RefMutToTemp(&'tcx Expr<'tcx>), + // Any other case + Expr(&'tcx Expr<'tcx>), +} + +impl<'tcx> ArgKind<'tcx> { + fn new(arg: &'tcx Expr<'tcx>) -> Self { + if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind { + if target.is_syntactic_place_expr() { + if arg.span.from_expansion() { + ArgKind::RefMutToPlaceAsMacro(arg) + } else { + ArgKind::RefMutToPlace(target) + } + } else { + ArgKind::RefMutToTemp(target) + } + } else { + ArgKind::Expr(arg) + } + } +} + +// Emits a note either on the temporary expression if it can be found in the same context as the +// base and returns `true`, or on the mutable reference to the temporary expression otherwise and +// returns `false`. +fn emit_note(diag: &mut Diag<'_, ()>, base: &Expr<'_>, expr: &Expr<'_>, expr_temp: &Expr<'_>) -> bool { + if base.span.eq_ctxt(expr.span) { + diag.span_note(expr_temp.span.source_callsite(), MSG_TEMPORARY); + true + } else { + diag.span_note(expr.span.source_callsite(), MSG_TEMPORARY_REFMUT); + false + } +} + +fn emit_lint_useless( + cx: &LateContext<'_>, + expr: &Expr<'_>, + left: &Expr<'_>, + right: &Expr<'_>, + left_temp: &Expr<'_>, + right_temp: &Expr<'_>, +) { + span_lint_and_then( + cx, + SWAP_WITH_TEMPORARY, + expr.span, + "swapping temporary values has no effect", + |diag| { + emit_note(diag, expr, left, left_temp); + emit_note(diag, expr, right, right_temp); + }, + ); +} + +fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, reftemp: &Expr<'_>, temp: &Expr<'_>) { + span_lint_and_then( + cx, + SWAP_WITH_TEMPORARY, + expr.span, + "swapping with a temporary value is inefficient", + |diag| { + if !emit_note(diag, expr, reftemp, temp) { + return; + } + + // Make the suggestion only when the original `swap()` call is a statement + // or the last expression in a block. + if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..) | Node::Block(..)) { + let mut applicability = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let assign_target = match target { + ArgKind::Expr(target) | ArgKind::RefMutToPlaceAsMacro(target) => { + Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref() + }, + ArgKind::RefMutToPlace(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability), + ArgKind::RefMutToTemp(_) => unreachable!(), + }; + let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability); + diag.span_suggestion( + expr.span, + "use assignment instead", + format!("{assign_target} = {assign_source}"), + applicability, + ); + } + }, + ); +} diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index ca42a9ac04e..f920f306bc1 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,11 +1,10 @@ use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint; 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 core::ops::ControlFlow; -use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; @@ -45,30 +44,32 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome) && let hir::ExprKind::Path(_) = args[0].kind { - span_lint_and_sugg( + span_lint( cx, UNNECESSARY_FILTER_MAP, expr.span, - format!("{name} is unnecessary"), - "try removing the filter_map", - String::new(), - Applicability::MaybeIncorrect, + String::from("this call to `.filter_map(..)` is unnecessary"), ); + return; + } + if name == "filter_map" { + "map(..)" + } else { + "map(..).next()" } - if name == "filter_map" { "map" } else { "map(..).next()" } } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(body.value).kind() { ty::Adt(adt, subst) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) && in_ty == subst.type_at(0) => { - if name == "filter_map" { "filter" } else { "find" } + if name == "filter_map" { "filter(..)" } else { "find(..)" } }, _ => return, } } else { return; }; - span_lint_and_sugg( + span_lint( cx, if name == "filter_map" { UNNECESSARY_FILTER_MAP @@ -76,10 +77,7 @@ 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"), - "try instead", - sugg.to_string(), - Applicability::MaybeIncorrect, + format!("this `.{name}(..)` can be written more simply using `.{sugg}`"), ); } } diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 9f4080100da..71e606add52 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -23,56 +23,61 @@ pub(super) fn check<'tcx>( let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); let is_bool = cx.typeck_results().expr_ty(recv).is_bool(); - if is_option || is_result || is_bool { - if let hir::ExprKind::Closure(&hir::Closure { body, fn_decl, .. }) = arg.kind { - let body = cx.tcx.hir_body(body); - let body_expr = &body.value; + if (is_option || is_result || is_bool) + && let hir::ExprKind::Closure(&hir::Closure { + body, + fn_decl, + kind: hir::ClosureKind::Closure, + .. + }) = arg.kind + { + let body = cx.tcx.hir_body(body); + let body_expr = &body.value; - if usage::BindingUsageFinder::are_params_used(cx, body) || is_from_proc_macro(cx, expr) { - return false; - } + if usage::BindingUsageFinder::are_params_used(cx, body) || is_from_proc_macro(cx, expr) { + return false; + } - if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { - let msg = if is_option { - "unnecessary closure used to substitute value for `Option::None`" - } else if is_result { - "unnecessary closure used to substitute value for `Result::Err`" - } else { - "unnecessary closure used with `bool::then`" - }; - let applicability = if body - .params - .iter() - // bindings are checked to be unused above - .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild)) - && matches!( - fn_decl.output, - FnRetTy::DefaultReturn(_) - | FnRetTy::Return(hir::Ty { - kind: hir::TyKind::Infer(()), - .. - }) - ) { - Applicability::MachineApplicable - } else { - // replacing the lambda may break type inference - Applicability::MaybeIncorrect - }; + if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { + let msg = if is_option { + "unnecessary closure used to substitute value for `Option::None`" + } else if is_result { + "unnecessary closure used to substitute value for `Result::Err`" + } else { + "unnecessary closure used with `bool::then`" + }; + let applicability = if body + .params + .iter() + // bindings are checked to be unused above + .all(|param| matches!(param.pat.kind, hir::PatKind::Binding(..) | hir::PatKind::Wild)) + && matches!( + fn_decl.output, + FnRetTy::DefaultReturn(_) + | FnRetTy::Return(hir::Ty { + kind: hir::TyKind::Infer(()), + .. + }) + ) { + Applicability::MachineApplicable + } else { + // replacing the lambda may break type inference + Applicability::MaybeIncorrect + }; - // This is a duplicate of what's happening in clippy_lints::methods::method_call, - // which isn't ideal, We want to get the method call span, - // but prefer to avoid changing the signature of the function itself. - if let hir::ExprKind::MethodCall(.., span) = expr.kind { - span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { - diag.span_suggestion_verbose( - span, - format!("use `{simplify_using}` instead"), - format!("{simplify_using}({})", snippet(cx, body_expr.span, "..")), - applicability, - ); - }); - return true; - } + // This is a duplicate of what's happening in clippy_lints::methods::method_call, + // which isn't ideal, We want to get the method call span, + // but prefer to avoid changing the signature of the function itself. + if let hir::ExprKind::MethodCall(.., span) = expr.kind { + span_lint_and_then(cx, UNNECESSARY_LAZY_EVALUATIONS, expr.span, msg, |diag| { + diag.span_suggestion_verbose( + span, + format!("use `{simplify_using}` instead"), + format!("{simplify_using}({})", snippet(cx, body_expr.span, "..")), + applicability, + ); + }); + return true; } } } diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index d7bd522ddab..b90748dd158 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -76,7 +76,7 @@ pub(super) fn check<'a>( && ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool)) && let non_binding_location = if path_to_local_id(l, hir_id) { r } else { l } && switch_to_eager_eval(cx, non_binding_location) - // xor, because if its both then thats a strange edge case and + // xor, because if its both then that's a strange edge case and // we can just ignore it, since by default clippy will error on this && (path_to_local_id(l, hir_id) ^ path_to_local_id(r, hir_id)) && !is_local_used(cx, non_binding_location, hir_id) @@ -92,7 +92,7 @@ pub(super) fn check<'a>( // we may need to add parens around the suggestion // in case the parent expression has additional method calls, // since for example `Some(5).map_or(false, |x| x == 5).then(|| 1)` - // being converted to `Some(5) == Some(5).then(|| 1)` isnt + // being converted to `Some(5) == Some(5).then(|| 1)` isn't // the same thing let inner_non_binding = Sugg::NonParen(Cow::Owned(format!( @@ -109,8 +109,8 @@ pub(super) fn check<'a>( let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) { match parent_expr.kind { - ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_par(), - ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_par(), + ExprKind::Binary(..) | ExprKind::Unary(..) | ExprKind::Cast(..) => binop.maybe_paren(), + ExprKind::MethodCall(_, receiver, _, _) if receiver.hir_id == expr.hir_id => binop.maybe_paren(), _ => binop, } } else { diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 3611b341897..b0cc7a785bc 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -8,6 +8,9 @@ use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_span::symbol::sym; +/// Checks if `expr`, of type `ty`, corresponds to a slice or can be dereferenced to a slice, or if +/// `expr` is a method call to `.iter()` on such a type. In these cases, return the slice-like +/// expression. pub(super) fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, diff --git a/clippy_lints/src/misc_early/builtin_type_shadow.rs b/clippy_lints/src/misc_early/builtin_type_shadow.rs index 662f7cd8500..9ee1e2f3fd1 100644 --- a/clippy_lints/src/misc_early/builtin_type_shadow.rs +++ b/clippy_lints/src/misc_early/builtin_type_shadow.rs @@ -6,14 +6,14 @@ use rustc_lint::EarlyContext; use super::BUILTIN_TYPE_SHADOW; pub(super) fn check(cx: &EarlyContext<'_>, param: &GenericParam) { - if let GenericParamKind::Type { .. } = param.kind { - if let Some(prim_ty) = PrimTy::from_name(param.ident.name) { - span_lint( - cx, - BUILTIN_TYPE_SHADOW, - param.ident.span, - format!("this generic shadows the built-in type `{}`", prim_ty.name()), - ); - } + if let GenericParamKind::Type { .. } = param.kind + && let Some(prim_ty) = PrimTy::from_name(param.ident.name) + { + span_lint( + cx, + BUILTIN_TYPE_SHADOW, + param.ident.span, + format!("this generic shadows the built-in type `{}`", prim_ty.name()), + ); } } diff --git a/clippy_lints/src/misc_early/redundant_pattern.rs b/clippy_lints/src/misc_early/redundant_pattern.rs index d5b5b2bf2dd..3cb51671aaf 100644 --- a/clippy_lints/src/misc_early/redundant_pattern.rs +++ b/clippy_lints/src/misc_early/redundant_pattern.rs @@ -6,20 +6,20 @@ use rustc_lint::EarlyContext; use super::REDUNDANT_PATTERN; pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { - if let PatKind::Ident(ann, ident, Some(ref right)) = pat.kind { - if let PatKind::Wild = right.kind { - span_lint_and_sugg( - cx, - REDUNDANT_PATTERN, - pat.span, - format!( - "the `{} @ _` pattern can be written as just `{}`", - ident.name, ident.name, - ), - "try", - format!("{}{}", ann.prefix_str(), ident.name), - Applicability::MachineApplicable, - ); - } + if let PatKind::Ident(ann, ident, Some(ref right)) = pat.kind + && let PatKind::Wild = right.kind + { + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN, + pat.span, + format!( + "the `{} @ _` pattern can be written as just `{}`", + ident.name, ident.name, + ), + "try", + format!("{}{}", ann.prefix_str(), ident.name), + Applicability::MachineApplicable, + ); } } diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs index 00f46629f10..fffaf40c9d1 100644 --- a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -7,30 +7,30 @@ use rustc_span::Span; use super::UNNEEDED_WILDCARD_PATTERN; pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { - if let PatKind::TupleStruct(_, _, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind { - if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) { - if let Some((left_index, left_pat)) = patterns[..rest_index] - .iter() - .rev() - .take_while(|pat| matches!(pat.kind, PatKind::Wild)) - .enumerate() - .last() - { - span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); - } + if let PatKind::TupleStruct(_, _, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind + && let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) + { + if let Some((left_index, left_pat)) = patterns[..rest_index] + .iter() + .rev() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); + } - if let Some((right_index, right_pat)) = patterns[rest_index + 1..] - .iter() - .take_while(|pat| matches!(pat.kind, PatKind::Wild)) - .enumerate() - .last() - { - span_lint( - cx, - patterns[rest_index].span.shrink_to_hi().to(right_pat.span), - right_index == 0, - ); - } + if let Some((right_index, right_pat)) = patterns[rest_index + 1..] + .iter() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint( + cx, + patterns[rest_index].span.shrink_to_hi().to(right_pat.span), + right_index == 0, + ); } } } diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index d52fe7e7d5b..394bc4aef1c 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -111,10 +111,10 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch { // Checks if impl_param_name is the same as one of type_param_names, // and is in a different position fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool { - if let Some(j) = type_param_names.get(impl_param_name) { - if i != *j { - return true; - } + if let Some(j) = type_param_names.get(impl_param_name) + && i != *j + { + return true; } false } diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index cdd6f4e5b03..c8e3462b24e 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -3,14 +3,15 @@ use std::ops::ControlFlow; use clippy_utils::comparisons::{Rel, normalize_comparison}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{eq_expr_value, hash_expr, higher}; -use rustc_ast::{LitKind, RangeLimits}; +use rustc_ast::{BinOpKind, LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; use rustc_errors::{Applicability, Diag}; -use rustc_hir::{BinOpKind, Block, Body, Expr, ExprKind, UnOp}; +use rustc_hir::{Block, Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; @@ -134,19 +135,31 @@ fn assert_len_expr<'hir>( cx: &LateContext<'_>, expr: &'hir Expr<'hir>, ) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { - if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) + let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind && let ExprKind::Binary(bin_op, left, right) = &condition.kind - - && let Some((cmp, asserted_len, slice_len)) = len_comparison(bin_op.node, left, right) - && let ExprKind::MethodCall(method, recv, [], _) = &slice_len.kind - && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() - && method.ident.name == sym::len - // check if `then` block has a never type expression && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind && cx.typeck_results().expr_ty(then_expr).is_never() { + len_comparison(bin_op.node, left, right)? + } else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| { + match cx.tcx.get_diagnostic_name(macro_call.def_id) { + Some(sym::assert_eq_macro) => Some((macro_call, BinOpKind::Eq)), + Some(sym::assert_ne_macro) => Some((macro_call, BinOpKind::Ne)), + _ => None, + } + }) && let Some((left, right, _)) = find_assert_eq_args(cx, expr, macro_call.expn) + { + len_comparison(bin_op, left, right)? + } else { + return None; + }; + + if let ExprKind::MethodCall(method, recv, [], _) = &slice_len.kind + && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() + && method.ident.name == sym::len + { Some((cmp, asserted_len, recv)) } else { None @@ -168,6 +181,7 @@ enum IndexEntry<'hir> { /// if the `assert!` asserts the right length. AssertWithIndex { highest_index: usize, + is_first_highest: bool, asserted_len: usize, assert_span: Span, slice: &'hir Expr<'hir>, @@ -177,6 +191,7 @@ enum IndexEntry<'hir> { /// Indexing without an `assert!` IndexWithoutAssert { highest_index: usize, + is_first_highest: bool, indexes: Vec<Span>, slice: &'hir Expr<'hir>, }, @@ -244,28 +259,41 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni assert_span, slice, } => { - *entry = IndexEntry::AssertWithIndex { - highest_index: index, - asserted_len: *asserted_len, - assert_span: *assert_span, - slice, - indexes: vec![expr.span], - comparison: *comparison, - }; + if slice.span.lo() > assert_span.lo() { + *entry = IndexEntry::AssertWithIndex { + highest_index: index, + is_first_highest: true, + asserted_len: *asserted_len, + assert_span: *assert_span, + slice, + indexes: vec![expr.span], + comparison: *comparison, + }; + } }, IndexEntry::IndexWithoutAssert { - highest_index, indexes, .. + highest_index, + indexes, + is_first_highest, + .. } | IndexEntry::AssertWithIndex { - highest_index, indexes, .. + highest_index, + indexes, + is_first_highest, + .. } => { indexes.push(expr.span); + if *is_first_highest { + (*is_first_highest) = *highest_index >= index; + } *highest_index = (*highest_index).max(index); }, } } else { indexes.push(IndexEntry::IndexWithoutAssert { highest_index: index, + is_first_highest: true, indexes: vec![expr.span], slice, }); @@ -284,15 +312,18 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un if let Some(entry) = entry { if let IndexEntry::IndexWithoutAssert { highest_index, + is_first_highest, indexes, slice, } = entry + && expr.span.lo() <= slice.span.lo() { *entry = IndexEntry::AssertWithIndex { highest_index: *highest_index, indexes: mem::take(indexes), + is_first_highest: *is_first_highest, slice, - assert_span: expr.span, + assert_span: expr.span.source_callsite(), comparison, asserted_len, }; @@ -301,7 +332,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un indexes.push(IndexEntry::StrayAssert { asserted_len, comparison, - assert_span: expr.span, + assert_span: expr.span.source_callsite(), slice, }); } @@ -325,12 +356,13 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_> match *entry { IndexEntry::AssertWithIndex { highest_index, + is_first_highest, asserted_len, ref indexes, comparison, assert_span, slice, - } if indexes.len() > 1 => { + } if indexes.len() > 1 && !is_first_highest => { // if we have found an `assert!`, let's also check that it's actually right // and if it covers the highest index and if not, suggest the correct length let sugg = match comparison { @@ -378,8 +410,9 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap<u64, Vec<IndexEntry<'_> IndexEntry::IndexWithoutAssert { ref indexes, highest_index, + is_first_highest, slice, - } if indexes.len() > 1 => { + } if indexes.len() > 1 && !is_first_highest => { // if there was no `assert!` but more than one index, suggest // adding an `assert!` that covers the highest index report_lint( diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 38a19dd2999..67537a251da 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -139,12 +139,11 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { // Const fns are not allowed as methods in a trait. { let parent = cx.tcx.hir_get_parent_item(hir_id).def_id; - if parent != CRATE_DEF_ID { - if let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) { - if let hir::ItemKind::Trait(..) = &item.kind { - return; - } - } + if parent != CRATE_DEF_ID + && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(parent) + && let hir::ItemKind::Trait(..) = &item.kind + { + return; } } @@ -156,9 +155,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { return; } - let mir = cx.tcx.optimized_mir(def_id); + let mir = cx.tcx.mir_drops_elaborated_and_const_checked(def_id); - if let Ok(()) = is_min_const_fn(cx, mir, self.msrv) + if let Ok(()) = is_min_const_fn(cx, &mir.borrow(), self.msrv) && let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) = cx.tcx.hir_node_by_def_id(def_id) { diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index 28dc2427428..1932d2d5f97 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -209,7 +209,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingFieldsInDebug { && let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, self_path_did) = self_path.res && cx.tcx.is_diagnostic_item(sym::Debug, trait_def_id) // don't trigger if this impl was derived - && !cx.tcx.has_attr(item.owner_id, sym::automatically_derived) + && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) && !item.span.from_expansion() // find `Debug::fmt` function && let Some(fmt_item) = items.iter().find(|i| i.ident.name == sym::fmt) @@ -224,11 +224,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingFieldsInDebug { // NB: can't call cx.typeck_results() as we are not in a body && let typeck_results = cx.tcx.typeck_body(*body_id) && should_lint(cx, typeck_results, block) - { // we intentionally only lint structs, see lint description - if let ItemKind::Struct(_, data, _) = &self_item.kind { - check_struct(cx, typeck_results, block, self_ty, item, data); - } + && let ItemKind::Struct(_, data, _) = &self_item.kind + { + check_struct(cx, typeck_results, block, self_ty, item, data); } } } diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index f49e03ea765..1f613171b46 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -160,12 +160,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { AssocItemContainer::Impl => cx.tcx.impl_trait_ref(container_id).map(|t| t.skip_binder().def_id), }; - if let Some(trait_def_id) = trait_def_id { - if trait_def_id.is_local() && !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) { - // If a trait is being implemented for an item, and the - // trait is not exported, we don't need #[inline] - return; - } + if let Some(trait_def_id) = trait_def_id + && trait_def_id.is_local() + && !cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) + { + // If a trait is being implemented for an item, and the + // trait is not exported, we don't need #[inline] + return; } let attrs = cx.tcx.hir_attrs(impl_item.hir_id()); diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index fbd287f5285..0e085585962 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -135,10 +135,10 @@ impl<'tcx> DivergenceVisitor<'_, 'tcx> { } fn report_diverging_sub_expr(&mut self, e: &Expr<'_>) { - if let Some(macro_call) = root_macro_call_first_node(self.cx, e) { - if self.cx.tcx.item_name(macro_call.def_id).as_str() == "todo" { - return; - } + if let Some(macro_call) = root_macro_call_first_node(self.cx, e) + && self.cx.tcx.item_name(macro_call.def_id).as_str() == "todo" + { + return; } span_lint(self.cx, DIVERGING_SUB_EXPRESSION, e.span, "sub-expression diverges"); } @@ -328,22 +328,22 @@ impl<'tcx> Visitor<'tcx> for ReadVisitor<'_, 'tcx> { return; } - if path_to_local_id(expr, self.var) { + if path_to_local_id(expr, self.var) // Check that this is a read, not a write. - if !is_in_assignment_position(self.cx, expr) { - span_lint_and_then( - self.cx, - MIXED_READ_WRITE_IN_EXPRESSION, - expr.span, - format!("unsequenced read of `{}`", self.cx.tcx.hir_name(self.var)), - |diag| { - diag.span_note( - self.write_expr.span, - "whether read occurs before this write depends on evaluation order", - ); - }, - ); - } + && !is_in_assignment_position(self.cx, expr) + { + span_lint_and_then( + self.cx, + MIXED_READ_WRITE_IN_EXPRESSION, + expr.span, + format!("unsequenced read of `{}`", self.cx.tcx.hir_name(self.var)), + |diag| { + diag.span_note( + self.write_expr.span, + "whether read occurs before this write depends on evaluation order", + ); + }, + ); } match expr.kind { // We're about to descend a closure. Since we don't know when (or @@ -373,10 +373,10 @@ impl<'tcx> Visitor<'tcx> for ReadVisitor<'_, 'tcx> { /// Returns `true` if `expr` is the LHS of an assignment, like `expr = ...`. fn is_in_assignment_position(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let Some(parent) = get_parent_expr(cx, expr) { - if let ExprKind::Assign(lhs, ..) = parent.kind { - return lhs.hir_id == expr.hir_id; - } + if let Some(parent) = get_parent_expr(cx, expr) + && let ExprKind::Assign(lhs, ..) = parent.kind + { + return lhs.hir_id == expr.hir_id; } false } diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 7287193326f..98614baffce 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -119,22 +119,22 @@ impl EarlyLintPass for ModStyle { } for folder in &folder_segments { - if !mod_folders.contains(folder) { - if let Some((file, path)) = file_map.get(folder) { - span_lint_and_then( - cx, - SELF_NAMED_MODULE_FILES, - Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), - format!("`mod.rs` files are required, found `{}`", path.display()), - |diag| { - let mut correct = path.to_path_buf(); - correct.pop(); - correct.push(folder); - correct.push("mod.rs"); - diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),)); - }, - ); - } + if !mod_folders.contains(folder) + && let Some((file, path)) = file_map.get(folder) + { + span_lint_and_then( + cx, + SELF_NAMED_MODULE_FILES, + Span::new(file.start_pos, file.start_pos, SyntaxContext::root(), None), + format!("`mod.rs` files are required, found `{}`", path.display()), + |diag| { + let mut correct = path.to_path_buf(); + correct.pop(); + correct.push(folder); + correct.push("mod.rs"); + diag.help(format!("move `{}` to `{}`", path.display(), correct.display(),)); + }, + ); } } } diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 2adc27c0b70..c6c27e22b90 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -142,10 +142,9 @@ fn collect_unsafe_exprs<'tcx>( .typeck_results() .type_dependent_def_id(expr.hir_id) .map(|def_id| cx.tcx.fn_sig(def_id)) + && sig.skip_binder().safety().is_unsafe() { - if sig.skip_binder().safety().is_unsafe() { - unsafe_ops.push(("unsafe method call occurs here", expr.span)); - } + unsafe_ops.push(("unsafe method call occurs here", expr.span)); } }, diff --git a/clippy_lints/src/mut_key.rs b/clippy_lints/src/mut_key.rs index 7abc5870d00..a45031ce22b 100644 --- a/clippy_lints/src/mut_key.rs +++ b/clippy_lints/src/mut_key.rs @@ -82,10 +82,10 @@ impl<'tcx> LateLintPass<'tcx> for MutableKeyType<'tcx> { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'tcx>) { - if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind { - if trait_ref_of_method(cx, item.owner_id.def_id).is_none() { - self.check_sig(cx, item.owner_id.def_id, sig.decl); - } + if let hir::ImplItemKind::Fn(ref sig, ..) = item.kind + && trait_ref_of_method(cx, item.owner_id.def_id).is_none() + { + self.check_sig(cx, item.owner_id.def_id, sig.decl); } } diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 3c4ba5141dd..d98c70e7f5a 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -77,16 +77,16 @@ impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { expr.span, "generally you want to avoid `&mut &mut _` if possible", ); - } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() { - if ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) { - span_lint_hir( - self.cx, - MUT_MUT, - expr.hir_id, - expr.span, - "this expression mutably borrows a mutable reference. Consider reborrowing", - ); - } + } else if let ty::Ref(_, ty, hir::Mutability::Mut) = self.cx.typeck_results().expr_ty(e).kind() + && ty.peel_refs().is_sized(self.cx.tcx, self.cx.typing_env()) + { + span_lint_hir( + self.cx, + MUT_MUT, + expr.hir_id, + expr.span, + "this expression mutably borrows a mutable reference. Consider reborrowing", + ); } } } diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 13a23a13b9c..270eebe0758 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -101,14 +101,13 @@ impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> { return; }, ExprKind::Path(_) => { - if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) { - if adj + if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) + && adj .iter() .any(|a| matches!(a.target.kind(), ty::Ref(_, _, Mutability::Mut))) - { - self.found = true; - return; - } + { + self.found = true; + return; } }, // Don't check await desugars diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 49fd29d1dd6..fe2157ca533 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -91,19 +91,19 @@ declare_lint_pass!(Mutex => [MUTEX_ATOMIC, MUTEX_INTEGER]); impl<'tcx> LateLintPass<'tcx> for Mutex { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { let ty = cx.typeck_results().expr_ty(expr); - if let ty::Adt(_, subst) = ty.kind() { - if is_type_diagnostic_item(cx, ty, sym::Mutex) { - let mutex_param = subst.type_at(0); - if let Some(atomic_name) = get_atomic_name(mutex_param) { - let msg = format!( - "consider using an `{atomic_name}` instead of a `Mutex` here; if you just want the locking \ + if let ty::Adt(_, subst) = ty.kind() + && is_type_diagnostic_item(cx, ty, sym::Mutex) + { + let mutex_param = subst.type_at(0); + if let Some(atomic_name) = get_atomic_name(mutex_param) { + let msg = format!( + "consider using an `{atomic_name}` instead of a `Mutex` here; if you just want the locking \ behavior and not the internal type, consider using `Mutex<()>`" - ); - match *mutex_param.kind() { - ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), - ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), - _ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg), - } + ); + match *mutex_param.kind() { + ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), + ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), + _ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg), } } } diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 2eacd6875d6..f768e11a4a2 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -154,7 +154,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { || is_receiver_of_method_call(cx, e) || is_as_argument(cx, e) { - snip = snip.maybe_par(); + snip = snip.maybe_paren(); } span_lint_and_sugg( @@ -426,10 +426,10 @@ fn fetch_bool_block(expr: &Expr<'_>) -> Option<Expression> { } fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> { - if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind { - if let LitKind::Bool(value) = lit_ptr.node { - return Some(value); - } + if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind + && let LitKind::Bool(value) = lit_ptr.node + { + return Some(value); } None } diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index b61061d076b..7052e1d0fbe 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -86,11 +86,11 @@ fn should_skip<'tcx>( return false; } - if let PatKind::Binding(.., name, _) = arg.pat.kind { + if let PatKind::Binding(.., name, _) = arg.pat.kind // If it's a potentially unused variable, we don't check it. - if name.name == kw::Underscore || name.as_str().starts_with('_') { - return true; - } + && (name.name == kw::Underscore || name.as_str().starts_with('_')) + { + return true; } // All spans generated from a proc-macro invocation are the same... @@ -164,13 +164,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { }; // Exclude non-inherent impls - if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { - if matches!( + if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) + && matches!( item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) - ) { - return; - } + ) + { + return; } let fn_sig = cx.tcx.fn_sig(fn_def_id).instantiate_identity(); @@ -353,10 +353,10 @@ impl MutablyUsedVariablesCtxt<'_> { for (parent, node) in self.tcx.hir_parent_iter(item) { if let Some(fn_sig) = self.tcx.hir_fn_sig_by_hir_id(parent) { return fn_sig.header.is_unsafe(); - } else if let Node::Block(block) = node { - if matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) { - return true; - } + } else if let Node::Block(block) = node + && matches!(block.rules, BlockCheckMode::UnsafeBlock(_)) + { + return true; } } false @@ -426,10 +426,10 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { // upon! self.add_mutably_used_var(*vid); } - } else if borrow == ty::BorrowKind::Immutable { + } else if borrow == ty::BorrowKind::Immutable // If there is an `async block`, it'll contain a call to a closure which we need to // go into to ensure all "mutate" checks are found. - if let Node::Expr(Expr { + && let Node::Expr(Expr { kind: ExprKind::Call( _, @@ -442,9 +442,8 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { ), .. }) = self.tcx.hir_node(cmt.hir_id) - { - self.async_closures.insert(*def_id); - } + { + self.async_closures.insert(*def_id); } } @@ -460,10 +459,9 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { }), .. } = &cmt.place + && !projections.is_empty() { - if !projections.is_empty() { - self.add_mutably_used_var(*vid); - } + self.add_mutably_used_var(*vid); } } @@ -477,10 +475,9 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { }), .. } = &cmt.place + && self.is_in_unsafe_block(id) { - if self.is_in_unsafe_block(id) { - self.add_mutably_used_var(*vid); - } + self.add_mutably_used_var(*vid); } self.prev_bind = None; } @@ -499,15 +496,14 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { }), .. } = &cmt.place + && let FakeReadCause::ForLet(Some(inner)) = cause { - if let FakeReadCause::ForLet(Some(inner)) = cause { - // Seems like we are inside an async function. We need to store the closure `DefId` - // to go through it afterwards. - self.async_closures.insert(inner); - self.add_alias(cmt.hir_id, *vid); - self.prev_move_to_closure.insert(*vid); - self.prev_bind = None; - } + // Seems like we are inside an async function. We need to store the closure `DefId` + // to go through it afterwards. + self.async_closures.insert(inner); + self.add_alias(cmt.hir_id, *vid); + self.prev_move_to_closure.insert(*vid); + self.prev_bind = None; } } @@ -522,10 +518,9 @@ impl<'tcx> euv::Delegate<'tcx> for MutablyUsedVariablesCtxt<'tcx> { }), .. } = &cmt.place + && self.is_in_unsafe_block(id) { - if self.is_in_unsafe_block(id) { - self.add_mutably_used_var(*vid); - } + self.add_mutably_used_var(*vid); } } } diff --git a/clippy_lints/src/needless_pass_by_value.rs b/clippy_lints/src/needless_pass_by_value.rs index 978fde33d51..275d710c76a 100644 --- a/clippy_lints/src/needless_pass_by_value.rs +++ b/clippy_lints/src/needless_pass_by_value.rs @@ -98,13 +98,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { } // Exclude non-inherent impls - if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { - if matches!( + if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) + && matches!( item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) - ) { - return; - } + ) + { + return; } // Allow `Borrow` or functions to be taken by value @@ -197,20 +197,18 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { { // Dereference suggestion let sugg = |diag: &mut Diag<'_, ()>| { - if let ty::Adt(def, ..) = ty.kind() { - if let Some(span) = cx.tcx.hir_span_if_local(def.did()) { - if type_allowed_to_implement_copy( - cx.tcx, - cx.param_env, - ty, - traits::ObligationCause::dummy_with_span(span), - rustc_hir::Safety::Safe, - ) - .is_ok() - { - diag.span_help(span, "or consider marking this type as `Copy`"); - } - } + if let ty::Adt(def, ..) = ty.kind() + && let Some(span) = cx.tcx.hir_span_if_local(def.did()) + && type_allowed_to_implement_copy( + cx.tcx, + cx.param_env, + ty, + traits::ObligationCause::dummy_with_span(span), + rustc_hir::Safety::Safe, + ) + .is_ok() + { + diag.span_help(span, "or consider marking this type as `Copy`"); } if is_type_diagnostic_item(cx, ty, sym::Vec) @@ -254,29 +252,28 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue { return; } - if is_type_lang_item(cx, ty, LangItem::String) { - if let Some(clone_spans) = + if is_type_lang_item(cx, ty, LangItem::String) + && let Some(clone_spans) = get_spans(cx, Some(body.id()), idx, &[("clone", ".to_string()"), ("as_str", "")]) - { + { + diag.span_suggestion( + input.span, + "consider changing the type to", + "&str", + Applicability::Unspecified, + ); + + for (span, suggestion) in clone_spans { diag.span_suggestion( - input.span, - "consider changing the type to", - "&str", + span, + span.get_source_text(cx) + .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), + suggestion, Applicability::Unspecified, ); - - for (span, suggestion) in clone_spans { - diag.span_suggestion( - span, - span.get_source_text(cx) - .map_or("change the call to".to_owned(), |src| format!("change `{src}` to")), - suggestion, - Applicability::Unspecified, - ); - } - - return; } + + return; } diag.span_suggestion_verbose( diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index cce0617ba39..4a86c3720ca 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -53,18 +53,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessUpdate { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Struct(_, fields, StructTailExpr::Base(base)) = expr.kind { let ty = cx.typeck_results().expr_ty(expr); - if let ty::Adt(def, _) = ty.kind() { - let variant = def.non_enum_variant(); - if fields.len() == variant.fields.len() - && !variant.is_field_list_non_exhaustive() - { - span_lint( - cx, - NEEDLESS_UPDATE, - base.span, - "struct update has no effect, all the fields in the struct have already been specified", - ); - } + if let ty::Adt(def, _) = ty.kind() + && fields.len() == def.non_enum_variant().fields.len() + && !def.variant(0_usize.into()).is_field_list_non_exhaustive() + { + span_lint( + cx, + NEEDLESS_UPDATE, + base.span, + "struct update has no effect, all the fields in the struct have already been specified", + ); } } } diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 429afff9b66..74c8142787e 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -16,9 +16,6 @@ declare_clippy_lint! { /// ### Why is this bad? /// It's more readable to just negate. /// - /// ### Known problems - /// This only catches integers (for now). - /// /// ### Example /// ```rust,ignore /// let a = x * -1; @@ -38,23 +35,32 @@ declare_lint_pass!(NegMultiply => [NEG_MULTIPLY]); impl<'tcx> LateLintPass<'tcx> for NegMultiply { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref op, left, right) = e.kind { - if BinOpKind::Mul == op.node { - match (&left.kind, &right.kind) { - (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {}, - (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right), - (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left), - _ => {}, - } + if let ExprKind::Binary(ref op, left, right) = e.kind + && BinOpKind::Mul == op.node + { + match (&left.kind, &right.kind) { + (&ExprKind::Unary(..), &ExprKind::Unary(..)) => {}, + (&ExprKind::Unary(UnOp::Neg, lit), _) => check_mul(cx, e.span, lit, right), + (_, &ExprKind::Unary(UnOp::Neg, lit)) => check_mul(cx, e.span, lit, left), + _ => {}, } } } } fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) { + const F16_ONE: u16 = 1.0_f16.to_bits(); + const F128_ONE: u128 = 1.0_f128.to_bits(); if let ExprKind::Lit(l) = lit.kind - && consts::lit_to_mir_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)) == Constant::Int(1) - && cx.typeck_results().expr_ty(exp).is_integral() + && matches!( + consts::lit_to_mir_constant(&l.node, cx.typeck_results().expr_ty_opt(lit)), + Constant::Int(1) + | Constant::F16(F16_ONE) + | Constant::F32(1.0) + | Constant::F64(1.0) + | Constant::F128(F128_ONE) + ) + && cx.typeck_results().expr_ty(exp).is_numeric() { let mut applicability = Applicability::MachineApplicable; let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability); diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 1e469c3b63a..4b73a4455f5 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -99,10 +99,10 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { let mut impls = HirIdSet::default(); for &d in cx.tcx.local_trait_impls(default_trait_id) { let ty = cx.tcx.type_of(d).instantiate_identity(); - if let Some(ty_def) = ty.ty_adt_def() { - if let Some(local_def_id) = ty_def.did().as_local() { - impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); - } + if let Some(ty_def) = ty.ty_adt_def() + && let Some(local_def_id) = ty_def.did().as_local() + { + impls.insert(cx.tcx.local_def_id_to_hir_id(local_def_id)); } } self.impling_types = Some(impls); diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 7187a8f2c11..7ab7976d569 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -182,23 +182,22 @@ impl NoEffect { ); return true; } - } else if let StmtKind::Let(local) = stmt.kind { - if !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id) - && !matches!(local.source, LocalSource::AsyncFn) - && let Some(init) = local.init - && local.els.is_none() - && !local.pat.span.from_expansion() - && has_no_effect(cx, init) - && let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind - && ident.name.to_ident_string().starts_with('_') - && !in_automatically_derived(cx.tcx, local.hir_id) - { - if let Some(l) = self.local_bindings.last_mut() { - l.push(hir_id); - self.underscore_bindings.insert(hir_id, ident.span); - } - return true; + } else if let StmtKind::Let(local) = stmt.kind + && !is_lint_allowed(cx, NO_EFFECT_UNDERSCORE_BINDING, local.hir_id) + && !matches!(local.source, LocalSource::AsyncFn) + && let Some(init) = local.init + && local.els.is_none() + && !local.pat.span.from_expansion() + && has_no_effect(cx, init) + && let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind + && ident.name.to_ident_string().starts_with('_') + && !in_automatically_derived(cx.tcx, local.hir_id) + { + if let Some(l) = self.local_bindings.last_mut() { + l.push(hir_id); + self.underscore_bindings.insert(hir_id, ident.span); } + return true; } false } diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 448bb603cf2..93865197ec9 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -1,6 +1,8 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::ty::implements_trait; -use clippy_utils::{is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core}; +use clippy_utils::{ + is_diag_trait_item, is_from_proc_macro, is_res_lang_ctor, last_path_segment, path_res, std_or_core, +}; use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; use rustc_hir::{Expr, ExprKind, ImplItem, ImplItemKind, LangItem, Node, UnOp}; @@ -98,7 +100,7 @@ declare_clippy_lint! { /// /// impl PartialOrd for A { /// fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - /// Some(self.cmp(other)) + /// Some(self.cmp(other)) // or self.cmp(other).into() /// } /// } /// ``` @@ -185,65 +187,66 @@ impl LateLintPass<'_> for NonCanonicalImpls { if block.stmts.is_empty() && let Some(expr) = block.expr - && expr_is_cmp(cx, &expr.kind, impl_item, &mut needs_fully_qualified) + && expr_is_cmp(cx, expr, impl_item, &mut needs_fully_qualified) { + return; } // Fix #12683, allow [`needless_return`] here else if block.expr.is_none() && let Some(stmt) = block.stmts.first() && let rustc_hir::StmtKind::Semi(Expr { - kind: ExprKind::Ret(Some(Expr { kind: ret_kind, .. })), + kind: ExprKind::Ret(Some(ret)), .. }) = stmt.kind - && expr_is_cmp(cx, ret_kind, impl_item, &mut needs_fully_qualified) + && expr_is_cmp(cx, ret, impl_item, &mut needs_fully_qualified) { - } else { - // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid - // suggestion tons more complex. - if let [lhs, rhs, ..] = trait_impl.args.as_slice() - && lhs != rhs - { - return; - } + return; + } + // If `Self` and `Rhs` are not the same type, bail. This makes creating a valid + // suggestion tons more complex. + else if let [lhs, rhs, ..] = trait_impl.args.as_slice() + && lhs != rhs + { + return; + } - span_lint_and_then( - cx, - NON_CANONICAL_PARTIAL_ORD_IMPL, - item.span, - "non-canonical implementation of `partial_cmp` on an `Ord` type", - |diag| { - let [_, other] = body.params else { - return; - }; - let Some(std_or_core) = std_or_core(cx) else { - return; - }; + span_lint_and_then( + cx, + NON_CANONICAL_PARTIAL_ORD_IMPL, + item.span, + "non-canonical implementation of `partial_cmp` on an `Ord` type", + |diag| { + let [_, other] = body.params else { + return; + }; + let Some(std_or_core) = std_or_core(cx) else { + return; + }; - let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { - (Some(other_ident), true) => vec![( + let suggs = match (other.pat.simple_ident(), needs_fully_qualified) { + (Some(other_ident), true) => vec![( + block.span, + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), + )], + (Some(other_ident), false) => { + vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] + }, + (None, true) => vec![ + ( block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, {})) }}", other_ident.name), - )], - (Some(other_ident), false) => { - vec![(block.span, format!("{{ Some(self.cmp({})) }}", other_ident.name))] - }, - (None, true) => vec![ - ( - block.span, - format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), - ), - (other.pat.span, "other".to_owned()), - ], - (None, false) => vec![ - (block.span, "{ Some(self.cmp(other)) }".to_owned()), - (other.pat.span, "other".to_owned()), - ], - }; + format!("{{ Some({std_or_core}::cmp::Ord::cmp(self, other)) }}"), + ), + (other.pat.span, "other".to_owned()), + ], + (None, false) => vec![ + (block.span, "{ Some(self.cmp(other)) }".to_owned()), + (other.pat.span, "other".to_owned()), + ], + }; - diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); - }, - ); - } + diag.multipart_suggestion("change this to", suggs, Applicability::Unspecified); + }, + ); } } } @@ -251,10 +254,11 @@ impl LateLintPass<'_> for NonCanonicalImpls { /// Return true if `expr_kind` is a `cmp` call. fn expr_is_cmp<'tcx>( cx: &LateContext<'tcx>, - expr_kind: &'tcx ExprKind<'tcx>, + expr: &'tcx Expr<'tcx>, impl_item: &ImplItem<'_>, needs_fully_qualified: &mut bool, ) -> bool { + let impl_item_did = impl_item.owner_id.def_id; if let ExprKind::Call( Expr { kind: ExprKind::Path(some_path), @@ -262,11 +266,17 @@ fn expr_is_cmp<'tcx>( .. }, [cmp_expr], - ) = expr_kind + ) = expr.kind { is_res_lang_ctor(cx, cx.qpath_res(some_path, *some_hir_id), LangItem::OptionSome) // Fix #11178, allow `Self::cmp(self, ..)` too - && self_cmp_call(cx, cmp_expr, impl_item.owner_id.def_id, needs_fully_qualified) + && self_cmp_call(cx, cmp_expr, impl_item_did, needs_fully_qualified) + } else if let ExprKind::MethodCall(_, recv, [], _) = expr.kind { + cx.tcx + .typeck(impl_item_did) + .type_dependent_def_id(expr.hir_id) + .is_some_and(|def_id| is_diag_trait_item(cx, def_id, sym::Into)) + && self_cmp_call(cx, recv, impl_item_did, needs_fully_qualified) } else { false } diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 9b53608ae7f..63859c0396e 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -449,7 +449,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { dereferenced_expr = parent_expr; }, - ExprKind::Index(e, _, _) if ptr::eq(&**e, cur_expr) => { + ExprKind::Index(e, _, _) if ptr::eq(&raw const **e, cur_expr) => { // `e[i]` => desugared to `*Index::index(&e, i)`, // meaning `e` must be referenced. // no need to go further up since a method call is involved now. diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs index 8305bf345ef..f6bc9428d65 100644 --- a/clippy_lints/src/non_std_lazy_statics.rs +++ b/clippy_lints/src/non_std_lazy_statics.rs @@ -37,7 +37,7 @@ declare_clippy_lint! { /// static FOO: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "FOO".to_lowercase()); /// static BAR: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| "BAR".to_lowercase()); /// ``` - #[clippy::version = "1.81.0"] + #[clippy::version = "1.86.0"] pub NON_STD_LAZY_STATICS, pedantic, "lazy static that could be replaced by `std::sync::LazyLock`" @@ -121,7 +121,7 @@ impl<'hir> LateLintPass<'hir> for NonStdLazyStatic { cx, NON_STD_LAZY_STATICS, macro_call.span, - "this macro has been superceded by `std::sync::LazyLock`", + "this macro has been superseded by `std::sync::LazyLock`", ); return; } @@ -240,7 +240,7 @@ impl LazyInfo { cx, NON_STD_LAZY_STATICS, self.ty_span_no_args, - "this type has been superceded by `LazyLock` in the standard library", + "this type has been superseded by `LazyLock` in the standard library", |diag| { diag.multipart_suggestion("use `std::sync::LazyLock` instead", suggs, appl); }, diff --git a/clippy_lints/src/non_zero_suggestions.rs b/clippy_lints/src/non_zero_suggestions.rs index 16c4391c0fb..635f5678e2a 100644 --- a/clippy_lints/src/non_zero_suggestions.rs +++ b/clippy_lints/src/non_zero_suggestions.rs @@ -82,11 +82,10 @@ fn check_non_zero_conversion(cx: &LateContext<'_>, expr: &Expr<'_>, applicabilit if let ty::Adt(adt_def, _) = receiver_ty.kind() && adt_def.is_struct() && cx.tcx.get_diagnostic_name(adt_def.did()) == Some(sym::NonZero) + && let Some(target_non_zero_type) = get_target_non_zero_type(target_ty) { - if let Some(target_non_zero_type) = get_target_non_zero_type(target_ty) { - let arg_snippet = get_arg_snippet(cx, arg, rcv_path); - suggest_non_zero_conversion(cx, expr, fn_name, target_non_zero_type, &arg_snippet, applicability); - } + let arg_snippet = get_arg_snippet(cx, arg, rcv_path); + suggest_non_zero_conversion(cx, expr, fn_name, target_non_zero_type, &arg_snippet, applicability); } } } diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index cf6b8992973..9b2cfd91b85 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -98,7 +98,7 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) let arg_snip = snippet(cx, arg_span, ".."); let expr_snip; let eq_impl; - if with_deref.is_implemented() { + if with_deref.is_implemented() && !arg_ty.peel_refs().is_str() { expr_snip = format!("*{arg_snip}"); eq_impl = with_deref; } else { diff --git a/clippy_lints/src/operators/float_equality_without_abs.rs b/clippy_lints/src/operators/float_equality_without_abs.rs index 74e0a6333db..047a5a0159c 100644 --- a/clippy_lints/src/operators/float_equality_without_abs.rs +++ b/clippy_lints/src/operators/float_equality_without_abs.rs @@ -50,7 +50,7 @@ pub(crate) fn check<'tcx>( // format the suggestion let suggestion = format!( "{}.abs()", - sugg::make_assoc(AssocOp::Binary(BinOpKind::Sub), &sug_l, &sug_r).maybe_par() + sugg::make_assoc(AssocOp::Binary(BinOpKind::Sub), &sug_l, &sug_r).maybe_paren() ); // spans the lint span_lint_and_then( diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 03582322827..e1fd09549a4 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -103,7 +103,7 @@ enum Parens { /// /// e.g. `-(x + y + 0)` cannot be reduced to `-x + y`, as the behavior changes silently. /// e.g. `1u64 + ((x + y + 0i32) as u64)` cannot be reduced to `1u64 + x + y as u64`, since -/// the the cast expression will not apply to the same expression. +/// the cast expression will not apply to the same expression. /// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced /// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be /// interpreted as a statement. The same behavior happens for `match`, `loop`, diff --git a/clippy_lints/src/operators/modulo_one.rs b/clippy_lints/src/operators/modulo_one.rs index fc5565e821e..2e6a071eb18 100644 --- a/clippy_lints/src/operators/modulo_one.rs +++ b/clippy_lints/src/operators/modulo_one.rs @@ -12,15 +12,15 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right: span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0"); } - if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() { - if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) { - span_lint( - cx, - MODULO_ONE, - expr.span, - "any number modulo -1 will panic/overflow or result in 0", - ); - } + if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() + && is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) + { + span_lint( + cx, + MODULO_ONE, + expr.span, + "any number modulo -1 will panic/overflow or result in 0", + ); } } } diff --git a/clippy_lints/src/operators/numeric_arithmetic.rs b/clippy_lints/src/operators/numeric_arithmetic.rs index 96c9775e292..e6be536ca0f 100644 --- a/clippy_lints/src/operators/numeric_arithmetic.rs +++ b/clippy_lints/src/operators/numeric_arithmetic.rs @@ -75,10 +75,10 @@ impl Context { hir::BodyOwnerKind::Static(_) | hir::BodyOwnerKind::Const { .. } => { let body_span = cx.tcx.hir_span_with_body(body_owner); - if let Some(span) = self.const_span { - if span.contains(body_span) { - return; - } + if let Some(span) = self.const_span + && span.contains(body_span) + { + return; } self.const_span = Some(body_span); }, @@ -90,10 +90,10 @@ impl Context { let body_owner = cx.tcx.hir_body_owner(body.id()); let body_span = cx.tcx.hir_span_with_body(body_owner); - if let Some(span) = self.const_span { - if span.contains(body_span) { - return; - } + if let Some(span) = self.const_span + && span.contains(body_span) + { + return; } self.const_span = None; } diff --git a/clippy_lints/src/operators/op_ref.rs b/clippy_lints/src/operators/op_ref.rs index 378fed481f4..0faa7b9e646 100644 --- a/clippy_lints/src/operators/op_ref.rs +++ b/clippy_lints/src/operators/op_ref.rs @@ -47,12 +47,11 @@ pub(crate) fn check<'tcx>( let rty = cx.typeck_results().expr_ty(r); let lcpy = is_copy(cx, lty); let rcpy = is_copy(cx, rty); - if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) { - if (are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) - || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty)) - { - return; // Don't lint - } + if let Some((self_ty, other_ty)) = in_impl(cx, e, trait_id) + && ((are_equal(cx, rty, self_ty) && are_equal(cx, lty, other_ty)) + || (are_equal(cx, rty, other_ty) && are_equal(cx, lty, self_ty))) + { + return; // Don't lint } // either operator autorefs or both args are copyable if (requires_ref || (lcpy && rcpy)) && implements_trait(cx, lty, trait_id, &[rty.into()]) { @@ -86,7 +85,7 @@ pub(crate) fn check<'tcx>( left.span, "use the left value directly", lsnip, - Applicability::MaybeIncorrect, // FIXME #2597 + Applicability::MachineApplicable, ); }, ); @@ -105,7 +104,7 @@ pub(crate) fn check<'tcx>( right.span, "use the right value directly", rsnip, - Applicability::MaybeIncorrect, // FIXME #2597 + Applicability::MachineApplicable, ); }, ); @@ -137,7 +136,7 @@ pub(crate) fn check<'tcx>( left.span, "use the left value directly", lsnip, - Applicability::MaybeIncorrect, // FIXME #2597 + Applicability::MachineApplicable, ); }, ); @@ -164,7 +163,7 @@ pub(crate) fn check<'tcx>( right.span, "use the right value directly", rsnip, - Applicability::MaybeIncorrect, // FIXME #2597 + Applicability::MachineApplicable, ); }); } diff --git a/clippy_lints/src/operators/verbose_bit_mask.rs b/clippy_lints/src/operators/verbose_bit_mask.rs index a6aba33e431..14773789141 100644 --- a/clippy_lints/src/operators/verbose_bit_mask.rs +++ b/clippy_lints/src/operators/verbose_bit_mask.rs @@ -32,7 +32,7 @@ pub(super) fn check<'tcx>( e.span, "bit mask could be simplified with a call to `trailing_zeros`", |diag| { - let sugg = Sugg::hir(cx, left1, "...").maybe_par(); + let sugg = Sugg::hir(cx, left1, "...").maybe_paren(); diag.span_suggestion( e.span, "try", diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 6f302ea1962..9487cec87ef 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -4,8 +4,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_copy; use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, eager_or_lazy, higher, is_else_clause, is_in_const_context, - is_res_lang_ctor, peel_blocks, peel_hir_expr_while, + CaptureKind, can_move_expr_to_closure, eager_or_lazy, expr_requires_coercion, higher, is_else_clause, + is_in_const_context, is_res_lang_ctor, peel_blocks, peel_hir_expr_while, }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -106,7 +106,7 @@ struct OptionOccurrence { fn format_option_in_sugg(cond_sugg: Sugg<'_>, as_ref: bool, as_mut: bool) -> String { format!( "{}{}", - cond_sugg.maybe_par(), + cond_sugg.maybe_paren(), if as_mut { ".as_mut()" } else if as_ref { @@ -212,6 +212,15 @@ fn try_get_option_occurrence<'tcx>( } } + let some_body_ty = cx.typeck_results().expr_ty(some_body); + let none_body_ty = cx.typeck_results().expr_ty(none_body); + // Check if coercion is needed for the `None` arm. If so, we cannot suggest because it will + // introduce a type mismatch. A special case is when both arms have the same type, then + // coercion is fine. + if some_body_ty != none_body_ty && expr_requires_coercion(cx, none_body) { + return None; + } + let mut app = Applicability::Unspecified; let (none_body, is_argless_call) = match none_body.kind { diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 65671b478ba..8eaf65e6306 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -38,7 +38,7 @@ impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { items: impl_items, .. }) = item.kind - && !cx.tcx.has_attr(item.owner_id, sym::automatically_derived) + && !cx.tcx.is_automatically_derived(item.owner_id.to_def_id()) && let Some(eq_trait) = cx.tcx.lang_items().eq_trait() && trait_ref.path.res.def_id() == eq_trait { diff --git a/clippy_lints/src/partialeq_to_none.rs b/clippy_lints/src/partialeq_to_none.rs index 6d4216970cc..9b9024c8105 100644 --- a/clippy_lints/src/partialeq_to_none.rs +++ b/clippy_lints/src/partialeq_to_none.rs @@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for PartialeqToNone { let sugg = format!( "{}.{}", sugg::Sugg::hir_with_applicability(cx, peel_ref_operators(cx, scrutinee), "..", &mut applicability) - .maybe_par(), + .maybe_paren(), if is_eq { "is_none()" } else { "is_some()" } ); diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 0a8e2885648..5d30b66def2 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -178,19 +178,18 @@ impl PassByRefOrValue { && size <= self.ref_min_size && let hir::TyKind::Ref(_, MutTy { ty: decl_ty, .. }) = input.kind { - if let Some(typeck) = cx.maybe_typeck_results() { + if let Some(typeck) = cx.maybe_typeck_results() // Don't lint if a raw pointer is created. // TODO: Limit the check only to raw pointers to the argument (or part of the argument) // which escape the current function. - if typeck.node_types().items().any(|(_, &ty)| ty.is_raw_ptr()) + && (typeck.node_types().items().any(|(_, &ty)| ty.is_raw_ptr()) || typeck .adjustments() .items() .flat_map(|(_, a)| a) - .any(|a| matches!(a.kind, Adjust::Pointer(PointerCoercion::UnsafeFnPointer))) - { - continue; - } + .any(|a| matches!(a.kind, Adjust::Pointer(PointerCoercion::UnsafeFnPointer)))) + { + continue; } let value_type = if fn_body.and_then(|body| body.params.get(index)).is_some_and(is_self) { "self".into() @@ -282,12 +281,11 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { } let attrs = cx.tcx.hir_attrs(hir_id); for a in attrs { - if let Some(meta_items) = a.meta_item_list() { - if a.has_name(sym::proc_macro_derive) - || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always)) - { - return; - } + if let Some(meta_items) = a.meta_item_list() + && (a.has_name(sym::proc_macro_derive) + || (a.has_name(sym::inline) && attr::list_contains_name(&meta_items, sym::always))) + { + return; } } }, @@ -296,13 +294,13 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue { } // Exclude non-inherent impls - if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { - if matches!( + if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) + && matches!( item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) - ) { - return; - } + ) + { + return; } self.check_poly_fn(cx, def_id, decl, Some(span)); diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs index b653b459b04..35caac855cf 100644 --- a/clippy_lints/src/pathbuf_init_then_push.rs +++ b/clippy_lints/src/pathbuf_init_then_push.rs @@ -173,16 +173,15 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { - if let Some(mut searcher) = self.searcher.take() { - if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind - && let ExprKind::MethodCall(name, self_arg, [arg_expr], _) = expr.kind - && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "push" - { - searcher.err_span = searcher.err_span.to(stmt.span); - searcher.arg = Some(*arg_expr); - searcher.display_err(cx); - } + if let Some(mut searcher) = self.searcher.take() + && let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind + && let ExprKind::MethodCall(name, self_arg, [arg_expr], _) = expr.kind + && path_to_local_id(self_arg, searcher.local_id) + && name.ident.as_str() == "push" + { + searcher.err_span = searcher.err_span.to(stmt.span); + searcher.arg = Some(*arg_expr); + searcher.display_err(cx); } } diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 96d3f7196c0..19d9acfc930 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -177,17 +177,16 @@ fn find_first_mismatch(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(Span, Mut PatKind::Or([p, ..]) => p, _ => p, }; - if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) { - if let [first, ..] = **adjustments { - if let ty::Ref(.., mutability) = *first.source.kind() { - let level = if p.hir_id == pat.hir_id { - Level::Top - } else { - Level::Lower - }; - result = Some((p.span, mutability, level)); - } - } + if let Some(adjustments) = cx.typeck_results().pat_adjustments().get(adjust_pat.hir_id) + && let [first, ..] = **adjustments + && let ty::Ref(.., mutability) = *first.source.kind() + { + let level = if p.hir_id == pat.hir_id { + Level::Top + } else { + Level::Lower + }; + result = Some((p.span, mutability, level)); } result.is_none() }); diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 901a1634ddc..491961408ad 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -264,8 +264,8 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { is_null_path(cx, l), is_null_path(cx, r), ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_par(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_par(), + (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), + (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), _ => return check_ptr_eq(cx, expr, op.node, l, r), }; @@ -498,29 +498,33 @@ fn check_fn_args<'cx, 'tcx: 'cx>( } fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { - if let FnRetTy::Return(ty) = sig.decl.output - && let Some((out, Mutability::Mut, _)) = get_ref_lm(ty) - { + let FnRetTy::Return(ty) = sig.decl.output else { return }; + for (out, mutability, out_span) in get_lifetimes(ty) { + if mutability != Some(Mutability::Mut) { + continue; + } let out_region = cx.tcx.named_bound_var(out.hir_id); - let args: Option<Vec<_>> = sig + // `None` if one of the types contains `&'a mut T` or `T<'a>`. + // Else, contains all the locations of `&'a T` types. + let args_immut_refs: Option<Vec<Span>> = sig .decl .inputs .iter() - .filter_map(get_ref_lm) + .flat_map(get_lifetimes) .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span)) + .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) .collect(); - if let Some(args) = args - && !args.is_empty() + if let Some(args_immut_refs) = args_immut_refs + && !args_immut_refs.is_empty() && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) { span_lint_and_then( cx, MUT_FROM_REF, - ty.span, + out_span, "mutable borrow from immutable input(s)", |diag| { - let ms = MultiSpan::from_spans(args); + let ms = MultiSpan::from_spans(args_immut_refs); diag.span_note(ms, "immutable borrow here"); }, ); @@ -686,12 +690,36 @@ fn matches_preds<'tcx>( }) } -fn get_ref_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> { - if let TyKind::Ref(lt, ref m) = ty.kind { - Some((lt, m.mutbl, ty.span)) - } else { - None +struct LifetimeVisitor<'tcx> { + result: Vec<(&'tcx Lifetime, Option<Mutability>, Span)>, +} + +impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let TyKind::Ref(lt, ref m) = ty.kind { + self.result.push((lt, Some(m.mutbl), ty.span)); + } + hir::intravisit::walk_ty(self, ty); } + + fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { + if let GenericArg::Lifetime(lt) = generic_arg { + self.result.push((lt, None, generic_arg.span())); + } + hir::intravisit::walk_generic_arg(self, generic_arg); + } +} + +/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. +/// +/// The second field of the vector's elements indicate if the lifetime is attached to a +/// shared reference, a mutable reference, or neither. +fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option<Mutability>, Span)> { + use hir::intravisit::VisitorExt as _; + + let mut visitor = LifetimeVisitor { result: Vec::new() }; + visitor.visit_ty_unambig(ty); + visitor.result } fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { @@ -728,8 +756,9 @@ fn check_ptr_eq<'tcx>( let (left_var, right_var) = (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); - if let Some(left_snip) = left_var.span.get_source_text(cx) - && let Some(right_snip) = right_var.span.get_source_text(cx) + let mut app = Applicability::MachineApplicable; + let left_snip = Sugg::hir_with_context(cx, left_var, expr.span.ctxt(), "_", &mut app); + let right_snip = Sugg::hir_with_context(cx, right_var, expr.span.ctxt(), "_", &mut app); { let Some(top_crate) = std_or_core(cx) else { return }; let invert = if op == BinOpKind::Eq { "" } else { "!" }; @@ -740,7 +769,7 @@ fn check_ptr_eq<'tcx>( format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), "try", format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), - Applicability::MachineApplicable, + app, ); } } @@ -748,7 +777,8 @@ fn check_ptr_eq<'tcx>( // If the given expression is a cast to a usize, return the lhs of the cast // E.g., `foo as *const _ as usize` returns `foo as *const _`. fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize + if !cast_expr.span.from_expansion() + && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize && let ExprKind::Cast(expr, _) = cast_expr.kind { Some(expr) @@ -759,7 +789,8 @@ fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_> // Peel raw casts if the remaining expression can be coerced to it fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> &'tcx Expr<'tcx> { - if let ExprKind::Cast(inner, _) = expr.kind + if !expr.span.from_expansion() + && let ExprKind::Cast(inner, _) = expr.kind && let ty::RawPtr(target_ty, _) = expr_ty.kind() && let inner_ty = cx.typeck_results().expr_ty(inner) && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs index 68ae575c906..7f74a2fff9f 100644 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ b/clippy_lints/src/ptr_offset_with_cast.rs @@ -77,10 +77,10 @@ impl<'tcx> LateLintPass<'tcx> for PtrOffsetWithCast { // If the given expression is a cast from a usize, return the lhs of the cast fn expr_as_cast_from_usize<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind { - if is_expr_ty_usize(cx, cast_lhs_expr) { - return Some(cast_lhs_expr); - } + if let ExprKind::Cast(cast_lhs_expr, _) = expr.kind + && is_expr_ty_usize(cx, cast_lhs_expr) + { + return Some(cast_lhs_expr); } None } @@ -91,14 +91,14 @@ fn expr_as_ptr_offset_call<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, Method)> { - if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind { - if is_expr_ty_raw_ptr(cx, arg_0) { - if path_segment.ident.name == sym::offset { - return Some((arg_0, arg_1, Method::Offset)); - } - if path_segment.ident.name.as_str() == "wrapping_offset" { - return Some((arg_0, arg_1, Method::WrappingOffset)); - } + if let ExprKind::MethodCall(path_segment, arg_0, [arg_1], _) = &expr.kind + && is_expr_ty_raw_ptr(cx, arg_0) + { + if path_segment.ident.name == sym::offset { + return Some((arg_0, arg_1, Method::Offset)); + } + if path_segment.ident.name.as_str() == "wrapping_offset" { + return Some((arg_0, arg_1, Method::WrappingOffset)); } } None diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index a80e1f79bbc..d318897443d 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -5,6 +5,7 @@ use clippy_config::types::MatchLintBehaviour; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{ eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, @@ -144,7 +145,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { && !span_contains_comment(cx.tcx.sess.source_map(), els.span) { let mut applicability = Applicability::MaybeIncorrect; - let init_expr_str = snippet_with_applicability(cx, init_expr.span, "..", &mut applicability); + let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); // Take care when binding is `ref` let sugg = if let PatKind::Binding( BindingMode(ByRef::Yes(ref_mutability), binding_mutability), diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index cc423eca74f..d292ed86ea4 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -179,10 +179,10 @@ impl_lint_pass!(Ranges => [ impl<'tcx> LateLintPass<'tcx> for Ranges { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(ref op, l, r) = expr.kind { - if self.msrv.meets(cx, msrvs::RANGE_CONTAINS) { - check_possible_range_contains(cx, op.node, l, r, expr, expr.span); - } + if let ExprKind::Binary(ref op, l, r) = expr.kind + && self.msrv.meets(cx, msrvs::RANGE_CONTAINS) + { + check_possible_range_contains(cx, op.node, l, r, expr, expr.span); } check_exclusive_range_plus_one(cx, expr); @@ -327,18 +327,18 @@ fn check_range_bounds<'a, 'tcx>(cx: &'a LateContext<'tcx>, ex: &'a Expr<'_>) -> inc: inclusive, }); } - } else if let Some(id) = path_to_local(r) { - if let Some(c) = ConstEvalCtxt::new(cx).eval(l) { - return Some(RangeBounds { - val: c, - expr: l, - id, - name_span: r.span, - val_span: l.span, - ord: ordering.reverse(), - inc: inclusive, - }); - } + } else if let Some(id) = path_to_local(r) + && let Some(c) = ConstEvalCtxt::new(cx).eval(l) + { + return Some(RangeBounds { + val: c, + expr: l, + id, + name_span: r.span, + val_span: l.span, + ord: ordering.reverse(), + inc: inclusive, + }); } } None @@ -361,8 +361,8 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { span, "an inclusive range would be more readable", |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_par(); + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); + let end = Sugg::hir(cx, y, "y").maybe_paren(); match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { Some(true) => { diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect); @@ -398,8 +398,8 @@ fn check_inclusive_range_minus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { expr.span, "an exclusive range would be more readable", |diag| { - let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string()); - let end = Sugg::hir(cx, y, "y").maybe_par(); + let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_paren().to_string()); + let end = Sugg::hir(cx, y, "y").maybe_paren(); diag.span_suggestion( expr.span, "use", diff --git a/clippy_lints/src/rc_clone_in_vec_init.rs b/clippy_lints/src/rc_clone_in_vec_init.rs index 6bb7650a7e1..689a2ac4c6a 100644 --- a/clippy_lints/src/rc_clone_in_vec_init.rs +++ b/clippy_lints/src/rc_clone_in_vec_init.rs @@ -1,14 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; -use clippy_utils::last_path_segment; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::{last_path_segment, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -135,7 +135,7 @@ fn ref_init(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(Symbol, Span)> { if let ty::Adt(adt, _) = *cx.typeck_results().expr_ty(expr).kind() && matches!(cx.tcx.get_diagnostic_name(adt.did()), Some(sym::RcWeak | sym::ArcWeak)) { - return Some((Symbol::intern("Weak"), func.span)); + return Some((sym::Weak, func.span)); } } diff --git a/clippy_lints/src/redundant_async_block.rs b/clippy_lints/src/redundant_async_block.rs index 8289ec47bc7..d2442ad0f37 100644 --- a/clippy_lints/src/redundant_async_block.rs +++ b/clippy_lints/src/redundant_async_block.rs @@ -1,14 +1,9 @@ -use std::ops::ControlFlow; - use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::peel_blocks; use clippy_utils::source::{snippet, walk_span_to_context}; use clippy_utils::ty::implements_trait; -use clippy_utils::visitors::for_each_expr_without_closures; +use clippy_utils::{desugar_await, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{ - Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, MatchSource, -}; +use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::UpvarCapture; use rustc_session::declare_lint_pass; @@ -99,20 +94,3 @@ fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Op None } } - -/// If `expr` is a desugared `.await`, return the original expression if it does not come from a -/// macro expansion. -fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind - && let ExprKind::Call(_, [into_future_arg]) = match_value.kind - && let ctxt = expr.span.ctxt() - && for_each_expr_without_closures(into_future_arg, |e| { - walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(())) - }) - .is_none() - { - Some(into_future_arg) - } else { - None - } -} diff --git a/clippy_lints/src/redundant_clone.rs b/clippy_lints/src/redundant_clone.rs index cfa622aea58..e57b8cc2d84 100644 --- a/clippy_lints/src/redundant_clone.rs +++ b/clippy_lints/src/redundant_clone.rs @@ -109,10 +109,10 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { continue; } - if let ty::Adt(def, _) = arg_ty.kind() { - if def.is_manually_drop() { - continue; - } + if let ty::Adt(def, _) = arg_ty.kind() + && def.is_manually_drop() + { + continue; } // `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }` @@ -182,20 +182,25 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let clone_usage = if local == ret_local { CloneUsage { - cloned_used: false, + cloned_use_loc: None.into(), cloned_consume_or_mutate_loc: None, clone_consumed_or_mutated: true, } } else { let clone_usage = visit_clone_usage(local, ret_local, mir, bb); - if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated { + if clone_usage.cloned_use_loc.maybe_used() && clone_usage.clone_consumed_or_mutated { // cloned value is used, and the clone is modified or moved continue; - } else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc { + } else if let MirLocalUsage::Used(loc) = clone_usage.cloned_use_loc + && possible_borrower.local_is_alive_at(ret_local, loc) + { + // cloned value is used, and the clone is alive. + continue; + } else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc // cloned value is mutated, and the clone is alive. - if possible_borrower.local_is_alive_at(ret_local, loc) { - continue; - } + && possible_borrower.local_is_alive_at(ret_local, loc) + { + continue; } clone_usage }; @@ -216,19 +221,18 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let call_snip = &snip[dot + 1..]; // Machine applicable when `call_snip` looks like `foobar()` - if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) { - if call_snip + if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) + && call_snip .as_bytes() .iter() .all(|b| b.is_ascii_alphabetic() || *b == b'_') - { - app = Applicability::MachineApplicable; - } + { + app = Applicability::MachineApplicable; } span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| { diag.span_suggestion(sugg_span, "remove this", "", app); - if clone_usage.cloned_used { + if clone_usage.cloned_use_loc.maybe_used() { diag.span_note(span, "cloned value is neither consumed nor mutated"); } else { diag.span_note( @@ -329,10 +333,33 @@ fn base_local_and_movability<'tcx>( (place.local, deref || field || slice) } -#[derive(Default)] +#[derive(Debug, Default)] +enum MirLocalUsage { + /// The local maybe used, but we are not sure how. + Unknown, + /// The local is not used. + #[default] + Unused, + /// The local is used at a specific location. + Used(mir::Location), +} + +impl MirLocalUsage { + fn maybe_used(&self) -> bool { + matches!(self, MirLocalUsage::Unknown | MirLocalUsage::Used(_)) + } +} + +impl From<Option<mir::Location>> for MirLocalUsage { + fn from(loc: Option<mir::Location>) -> Self { + loc.map_or(MirLocalUsage::Unused, MirLocalUsage::Used) + } +} + +#[derive(Debug, Default)] struct CloneUsage { - /// Whether the cloned value is used after the clone. - cloned_used: bool, + /// The first location where the cloned value is used, if any. + cloned_use_loc: MirLocalUsage, /// The first location where the cloned value is consumed or mutated, if any. cloned_consume_or_mutate_loc: Option<mir::Location>, /// Whether the clone value is mutated. @@ -360,7 +387,7 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, .map(|mut vec| (vec.remove(0), vec.remove(0))) { CloneUsage { - cloned_used: !cloned_use_locs.is_empty(), + cloned_use_loc: cloned_use_locs.first().copied().into(), cloned_consume_or_mutate_loc: cloned_consume_or_mutate_locs.first().copied(), // Consider non-temporary clones consumed. // TODO: Actually check for mutation of non-temporaries. @@ -369,7 +396,7 @@ fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, } } else { CloneUsage { - cloned_used: true, + cloned_use_loc: MirLocalUsage::Unknown, cloned_consume_or_mutate_loc: None, clone_consumed_or_mutated: true, } diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 1498a49a7a4..84597269a58 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall { // avoid clippy::double_parens if !is_in_fn_call_arg { - hint = hint.maybe_par(); + hint = hint.maybe_paren(); } diag.span_suggestion(full_expr.span, "try doing something like", hint, applicability); diff --git a/clippy_lints/src/redundant_pub_crate.rs b/clippy_lints/src/redundant_pub_crate.rs index f2fdac5a8af..7b381fac5f1 100644 --- a/clippy_lints/src/redundant_pub_crate.rs +++ b/clippy_lints/src/redundant_pub_crate.rs @@ -52,13 +52,10 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { && is_not_macro_export(item) && !item.span.in_external_macro(cx.sess().source_map()) { - // FIXME: `DUMMY_SP` isn't right here, because it causes the - // resulting span to begin at the start of the file. - let span = item.span.with_hi( - item.kind - .ident() - .map_or(rustc_span::DUMMY_SP.hi(), |ident| ident.span.hi()), - ); + let span = item + .kind + .ident() + .map_or(item.span, |ident| item.span.with_hi(ident.span.hi())); let descr = cx.tcx.def_kind(item.owner_id).descr(item.owner_id.to_def_id()); span_lint_and_then( cx, diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 7038b19d275..1117dea703c 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -135,25 +135,24 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing { }; diag.span_suggestion(expr.span, help_msg, sugg, app); }); - } else if let Some(target_id) = cx.tcx.lang_items().deref_target() { - if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( + } else if let Some(target_id) = cx.tcx.lang_items().deref_target() + && let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions( cx.typing_env(), Ty::new_projection_from_args(cx.tcx, target_id, cx.tcx.mk_args(&[GenericArg::from(indexed_ty)])), - ) { - if deref_ty == expr_ty { - let (lint, msg) = DEREF_BY_SLICING_LINT; - span_lint_and_then(cx, lint, expr.span, msg, |diag| { - let mut app = Applicability::MachineApplicable; - let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; - let sugg = if needs_parens_for_prefix { - format!("(&{}{}*{snip})", mutability.prefix_str(), "*".repeat(indexed_ref_count)) - } else { - format!("&{}{}*{snip}", mutability.prefix_str(), "*".repeat(indexed_ref_count)) - }; - diag.span_suggestion(expr.span, "dereference the original value instead", sugg, app); - }); - } - } + ) + && deref_ty == expr_ty + { + let (lint, msg) = DEREF_BY_SLICING_LINT; + span_lint_and_then(cx, lint, expr.span, msg, |diag| { + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0; + let sugg = if needs_parens_for_prefix { + format!("(&{}{}*{snip})", mutability.prefix_str(), "*".repeat(indexed_ref_count)) + } else { + format!("&{}{}*{snip}", mutability.prefix_str(), "*".repeat(indexed_ref_count)) + }; + diag.span_suggestion(expr.span, "dereference the original value instead", sugg, app); + }); } } } diff --git a/clippy_lints/src/redundant_test_prefix.rs b/clippy_lints/src/redundant_test_prefix.rs new file mode 100644 index 00000000000..84276e32165 --- /dev/null +++ b/clippy_lints/src/redundant_test_prefix.rs @@ -0,0 +1,161 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_test_function; +use clippy_utils::visitors::for_each_expr; +use rustc_errors::Applicability; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{self as hir, Body, ExprKind, FnDecl}; +use rustc_lexer::is_ident; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::def_id::LocalDefId; +use rustc_span::{Span, Symbol, edition}; +use std::borrow::Cow; +use std::ops::ControlFlow; + +declare_clippy_lint! { + /// ### What it does + /// Checks for test functions (functions annotated with `#[test]`) that are prefixed + /// with `test_` which is redundant. + /// + /// ### Why is this bad? + /// This is redundant because test functions are already annotated with `#[test]`. + /// Moreover, it clutters the output of `cargo test` since test functions are expanded as + /// `module::tests::test_use_case` in the output. Without the redundant prefix, the output + /// becomes `module::tests::use_case`, which is more readable. + /// + /// ### Example + /// ```no_run + /// #[cfg(test)] + /// mod tests { + /// use super::*; + /// + /// #[test] + /// fn test_use_case() { + /// // test code + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// #[cfg(test)] + /// mod tests { + /// use super::*; + /// + /// #[test] + /// fn use_case() { + /// // test code + /// } + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub REDUNDANT_TEST_PREFIX, + restriction, + "redundant `test_` prefix in test function name" +} + +declare_lint_pass!(RedundantTestPrefix => [REDUNDANT_TEST_PREFIX]); + +impl<'tcx> LateLintPass<'tcx> for RedundantTestPrefix { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'_>, + _decl: &FnDecl<'_>, + body: &'tcx Body<'_>, + _span: Span, + fn_def_id: LocalDefId, + ) { + // Ignore methods and closures. + let FnKind::ItemFn(ref ident, ..) = kind else { + return; + }; + + // Skip the lint if the function is within a macro expansion. + if ident.span.from_expansion() { + return; + } + + // Skip if the function name does not start with `test_`. + if !ident.as_str().starts_with("test_") { + return; + } + + // If the function is not a test function, skip the lint. + if !is_test_function(cx.tcx, fn_def_id) { + return; + } + + span_lint_and_then( + cx, + REDUNDANT_TEST_PREFIX, + ident.span, + "redundant `test_` prefix in test function name", + |diag| { + let non_prefixed = Symbol::intern(ident.as_str().trim_start_matches("test_")); + if is_invalid_ident(non_prefixed) { + // If the prefix-trimmed name is not a valid function name, do not provide an + // automatic fix, just suggest renaming the function. + diag.help( + "consider function renaming (just removing `test_` prefix will produce invalid function name)", + ); + } else { + let (sugg, msg): (Cow<'_, str>, _) = if name_conflicts(cx, body, non_prefixed) { + // If `non_prefixed` conflicts with another function in the same module/scope, + // do not provide an automatic fix, but still emit a fix suggestion. + ( + format!("{non_prefixed}_works").into(), + "consider function renaming (just removing `test_` prefix will cause a name conflict)", + ) + } else { + // If `non_prefixed` is a valid identifier and does not conflict with another function, + // so we can suggest an auto-fix. + (non_prefixed.as_str().into(), "consider removing the `test_` prefix") + }; + diag.span_suggestion(ident.span, msg, sugg, Applicability::MaybeIncorrect); + } + }, + ); + } +} + +/// Checks whether removal of the `_test` prefix from the function name will cause a name conflict. +/// +/// There should be no other function with the same name in the same module/scope. Also, there +/// should not be any function call with the same name within the body of the function, to avoid +/// recursion. +fn name_conflicts<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, fn_name: Symbol) -> bool { + let tcx = cx.tcx; + let id = body.id().hir_id; + + // Iterate over items in the same module/scope + let (module, _module_span, _module_hir) = tcx.hir_get_module(tcx.parent_module(id)); + if module + .item_ids + .iter() + .any(|item| matches!(tcx.hir_item(*item).kind, hir::ItemKind::Fn { ident, .. } if ident.name == fn_name)) + { + // Name conflict found + return true; + } + + // Also check that within the body of the function there is also no function call + // with the same name (since it will result in recursion) + for_each_expr(cx, body, |expr| { + if let ExprKind::Path(qpath) = &expr.kind + && let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id() + && let Some(name) = tcx.opt_item_name(def_id) + && name == fn_name + { + // Function call with the same name found + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + +fn is_invalid_ident(ident: Symbol) -> bool { + // The identifier is either a reserved keyword, or starts with an invalid sequence. + ident.is_reserved(|| edition::LATEST_STABLE_EDITION) || !is_ident(ident.as_str()) +} diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 9443dca154e..834ff2af0e8 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths}; +use clippy_utils::{def_path_res_with_base, find_crates, path_def_id, paths, sym}; use rustc_ast::ast::{LitKind, StrStyle}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{BorrowKind, Expr, ExprKind, OwnerId}; @@ -76,7 +76,7 @@ declare_clippy_lint! { /// This is documented as an antipattern [on the regex documentation](https://docs.rs/regex/latest/regex/#avoid-re-compiling-regexes-especially-in-a-loop) /// /// ### Example - /// ```no_run + /// ```rust,ignore /// # let haystacks = [""]; /// # const MY_REGEX: &str = "a.b"; /// for haystack in haystacks { @@ -87,7 +87,7 @@ declare_clippy_lint! { /// } /// ``` /// can be replaced with - /// ```no_run + /// ```rust,ignore /// # let haystacks = [""]; /// # const MY_REGEX: &str = "a.b"; /// let regex = regex::Regex::new(MY_REGEX).unwrap(); @@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for Regex { // // `def_path_res_with_base` will resolve through re-exports but is relatively heavy, so we only // perform the operation once and store the results - let regex_crates = find_crates(cx.tcx, sym!(regex)); + let regex_crates = find_crates(cx.tcx, sym::regex); let mut resolve = |path: &[&str], kind: RegexKind| { for res in def_path_res_with_base(cx.tcx, regex_crates.clone(), &path[1..]) { if let Some(id) = res.opt_def_id() { diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 33815cc3bac..226e8ff6adb 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -3,6 +3,7 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::AssocItem; use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; @@ -85,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { .associated_items(did) .in_definition_order() .filter(|assoc_item| assoc_item.is_fn()) - .map(|assoc_item| assoc_item.name()) + .map(AssocItem::name) .collect() } else { BTreeSet::new() diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index 6a0dfde2d9c..a8c6518b592 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -32,28 +32,28 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi { }) = item.kind { let did = trait_ref.path.res.def_id(); - if let Some(visit_did) = get_trait_def_id(cx.tcx, &paths::SERDE_DE_VISITOR) { - if did == visit_did { - let mut seen_str = None; - let mut seen_string = None; - for item in *items { - match item.ident.as_str() { - "visit_str" => seen_str = Some(item.span), - "visit_string" => seen_string = Some(item.span), - _ => {}, - } - } - if let Some(span) = seen_string { - if seen_str.is_none() { - span_lint( - cx, - SERDE_API_MISUSE, - span, - "you should not implement `visit_string` without also implementing `visit_str`", - ); - } + if let Some(visit_did) = get_trait_def_id(cx.tcx, &paths::SERDE_DE_VISITOR) + && did == visit_did + { + let mut seen_str = None; + let mut seen_string = None; + for item in *items { + match item.ident.as_str() { + "visit_str" => seen_str = Some(item.span), + "visit_string" => seen_string = Some(item.span), + _ => {}, } } + if let Some(span) = seen_string + && seen_str.is_none() + { + span_lint( + cx, + SERDE_API_MISUSE, + span, + "you should not implement `visit_string` without also implementing `visit_str`", + ); + } } } } diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index 1185d67b125..ff6e6ef214b 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -3,12 +3,12 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{SpanlessEq, higher, peel_hir_expr_while}; +use clippy_utils::{SpanlessEq, higher, peel_hir_expr_while, sym}; use rustc_hir::{Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::Span; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for SetContainsOrInsert { then: then_expr, .. }) = higher::If::hir(expr) - && let Some((contains_expr, sym)) = try_parse_op_call(cx, cond_expr, sym!(contains))//try_parse_contains(cx, cond_expr) + && let Some((contains_expr, sym)) = try_parse_op_call(cx, cond_expr, sym::contains)//try_parse_contains(cx, cond_expr) && let Some(insert_expr) = find_insert_calls(cx, &contains_expr, then_expr) { span_lint( @@ -118,7 +118,7 @@ fn find_insert_calls<'tcx>( expr: &'tcx Expr<'_>, ) -> Option<OpExpr<'tcx>> { for_each_expr(cx, expr, |e| { - if let Some((insert_expr, _)) = try_parse_op_call(cx, e, sym!(insert)) + if let Some((insert_expr, _)) = try_parse_op_call(cx, e, sym::insert) && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver) && SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value) { diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index b82ddedd56c..14399867f31 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -8,7 +8,9 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::Res; use rustc_hir::def_id::LocalDefId; use rustc_hir::hir_id::ItemLocalId; -use rustc_hir::{Block, Body, BodyOwnerKind, Expr, ExprKind, HirId, LetExpr, Node, Pat, PatKind, QPath, UnOp}; +use rustc_hir::{ + Block, Body, BodyOwnerKind, Expr, ExprKind, HirId, LetExpr, LocalSource, Node, Pat, PatKind, QPath, UnOp, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -65,7 +67,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub SHADOW_REUSE, restriction, - "rebinding a name to an expression that re-uses the original value, e.g., `let x = x + 1`" + "rebinding a name to an expression that reuses the original value, e.g., `let x = x + 1`" } declare_clippy_lint! { @@ -125,6 +127,17 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { return; } + // Desugaring of a destructuring assignment may reuse the same identifier internally. + // Peel `Pat` and `PatField` nodes and check if we reach a desugared `Let` assignment. + if let Some((_, Node::LetStmt(let_stmt))) = cx + .tcx + .hir_parent_iter(pat.hir_id) + .find(|(_, node)| !matches!(node, Node::Pat(_) | Node::PatField(_))) + && let LocalSource::AssignDesugar(_) = let_stmt.source + { + return; + } + let HirId { owner, local_id } = id; // get (or insert) the list of items for this owner and symbol let (ref mut data, scope_owner) = *self.bindings.last_mut().unwrap(); @@ -167,10 +180,10 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool { let scope_tree = cx.tcx.region_scope_tree(owner.to_def_id()); - if let Some(first_scope) = scope_tree.var_scope(first) { - if let Some(second_scope) = scope_tree.var_scope(second) { - return scope_tree.is_subscope_of(second_scope, first_scope); - } + if let Some(first_scope) = scope_tree.var_scope(first) + && let Some(second_scope) = scope_tree.var_scope(second) + { + return scope_tree.is_subscope_of(second_scope, first_scope); } false diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index 76874cc3420..ccb1209c6fc 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -124,8 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> { diag.span_label( apa.first_block_span, format!( - "temporary `{}` is currently being dropped at the end of its contained scope", - first_bind_ident + "temporary `{first_bind_ident}` is currently being dropped at the end of its contained scope" ), ); }, @@ -145,7 +144,10 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { Self { cx, type_cache } } - fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool { + fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { + if !self.cx.tcx.recursion_limit().value_within_limit(depth) { + return false; + } let ty = self .cx .tcx @@ -157,12 +159,12 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { e.insert(false); }, } - let value = self.has_sig_drop_attr_uncached(ty); + let value = self.has_sig_drop_attr_uncached(ty, depth + 1); self.type_cache.insert(ty, value); value } - fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>) -> bool { + fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { if let Some(adt) = ty.ty_adt_def() { let mut iter = get_attr( self.cx.sess(), @@ -177,15 +179,15 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { rustc_middle::ty::Adt(a, b) => { for f in a.all_fields() { let ty = f.ty(self.cx.tcx, b); - if self.has_sig_drop_attr(ty) { + if self.has_sig_drop_attr(ty, depth) { return true; } } for generic_arg in *b { - if let GenericArgKind::Type(ty) = generic_arg.unpack() { - if self.has_sig_drop_attr(ty) { - return true; - } + if let GenericArgKind::Type(ty) = generic_arg.unpack() + && self.has_sig_drop_attr(ty, depth) + { + return true; } } false @@ -193,7 +195,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { rustc_middle::ty::Array(ty, _) | rustc_middle::ty::RawPtr(ty, _) | rustc_middle::ty::Ref(_, ty, _) - | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(*ty), + | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(*ty, depth), _ => false, } } @@ -269,7 +271,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { apa.has_expensive_expr_after_last_attr = false; }; let mut ac = AttrChecker::new(self.cx, self.type_cache); - if ac.has_sig_drop_attr(self.cx.typeck_results().expr_ty(expr)) { + if ac.has_sig_drop_attr(self.cx.typeck_results().expr_ty(expr), 0) { if let hir::StmtKind::Let(local) = self.ap.curr_stmt.kind && let hir::PatKind::Binding(_, hir_id, ident, _) = local.pat.kind && !self.ap.apas.contains_key(&hir_id) @@ -317,7 +319,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { } }, hir::StmtKind::Semi(semi_expr) => { - if has_drop(semi_expr, &apa.first_bind_ident, self.cx) { + if has_drop(semi_expr, apa.first_bind_ident, self.cx) { apa.has_expensive_expr_after_last_attr = false; apa.last_stmt_span = DUMMY_SP; return; @@ -414,7 +416,7 @@ fn dummy_stmt_expr<'any>(expr: &'any hir::Expr<'any>) -> hir::Stmt<'any> { } } -fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: &Option<Ident>, lcx: &LateContext<'_>) -> bool { +fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: Option<Ident>, lcx: &LateContext<'_>) -> bool { if let hir::ExprKind::Call(fun, [first_arg]) = expr.kind && let hir::ExprKind::Path(hir::QPath::Resolved(_, fun_path)) = &fun.kind && let Res::Def(DefKind::Fn, did) = fun_path.res @@ -424,7 +426,7 @@ fn has_drop(expr: &hir::Expr<'_>, first_bind_ident: &Option<Ident>, lcx: &LateCo if let hir::ExprKind::Path(hir::QPath::Resolved(_, arg_path)) = &local_expr.kind && let [first_arg_ps, ..] = arg_path.segments && let Some(first_bind_ident) = first_bind_ident - && &first_arg_ps.ident == first_bind_ident + && first_arg_ps.ident == first_bind_ident { true } else { diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index 50a6ee316c8..8c34da0d14a 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -45,19 +45,20 @@ impl EarlyLintPass for SingleCharLifetimeNames { return; } - if let GenericParamKind::Lifetime = param.kind { - if !param.is_placeholder && param.ident.as_str().len() <= 2 { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - ctx, - SINGLE_CHAR_LIFETIME_NAMES, - param.ident.span, - "single-character lifetime names are likely uninformative", - |diag| { - diag.help("use a more informative name"); - }, - ); - } + if let GenericParamKind::Lifetime = param.kind + && !param.is_placeholder + && param.ident.as_str().len() <= 2 + { + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + ctx, + SINGLE_CHAR_LIFETIME_NAMES, + param.ident.span, + "single-character lifetime names are likely uninformative", + |diag| { + diag.help("use a more informative name"); + }, + ); } } } diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 35f80b2acda..62939912304 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -204,17 +204,17 @@ impl SingleComponentPathImports { if let UseTreeKind::Nested { items, .. } = &use_tree.kind { for tree in items { let segments = &tree.0.prefix.segments; - if segments.len() == 1 { - if let UseTreeKind::Simple(None) = tree.0.kind { - let name = segments[0].ident.name; - if !macros.contains(&name) { - single_use_usages.push(SingleUse { - name, - span: tree.0.span, - item_id: item.id, - can_suggest: false, - }); - } + if segments.len() == 1 + && let UseTreeKind::Simple(None) = tree.0.kind + { + let name = segments[0].ident.name; + if !macros.contains(&name) { + single_use_usages.push(SingleUse { + name, + span: tree.0.span, + item_id: item.id, + can_suggest: false, + }); } } } diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 27c548bed9f..43a3e696105 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -3,7 +3,7 @@ use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::ty::is_type_lang_item; use clippy_utils::{ SpanlessEq, get_expr_use_or_unification_node, get_parent_expr, is_lint_allowed, method_calls, path_def_id, - peel_blocks, + peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; @@ -12,7 +12,6 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::sym; use std::ops::ControlFlow; @@ -162,13 +161,12 @@ impl<'tcx> LateLintPass<'tcx> for StringAdd { if is_string(cx, left) { if !is_lint_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) { let parent = get_parent_expr(cx, e); - if let Some(p) = parent { - if let ExprKind::Assign(target, _, _) = p.kind { + if let Some(p) = parent + && let ExprKind::Assign(target, _, _) = p.kind // avoid duplicate matches - if SpanlessEq::new(cx).eq_expr(target, left) { - return; - } - } + && SpanlessEq::new(cx).eq_expr(target, left) + { + return; } } span_lint( @@ -263,7 +261,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { && let ExprKind::AddrOf(BorrowKind::Ref, _, args) = bytes_arg.kind && let ExprKind::Index(left, right, _) = args.kind && let (method_names, expressions, _) = method_calls(left, 1) - && method_names == [sym!(as_bytes)] + && method_names == [sym::as_bytes] && expressions.len() == 1 && expressions[0].1.is_empty() diff --git a/clippy_lints/src/suspicious_trait_impl.rs b/clippy_lints/src/suspicious_trait_impl.rs index 56bd8fefdb4..83241f97a99 100644 --- a/clippy_lints/src/suspicious_trait_impl.rs +++ b/clippy_lints/src/suspicious_trait_impl.rs @@ -59,25 +59,18 @@ impl<'tcx> LateLintPass<'tcx> for SuspiciousImpl { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { match expr.kind { hir::ExprKind::Binary(op, _, _) => { - self.check_expr_inner(cx, expr, op.node, op.span); - } + check_expr_inner(cx, expr, op.node, op.span); + }, hir::ExprKind::AssignOp(op, _, _) => { - self.check_expr_inner(cx, expr, op.node.into(), op.span); - } - _ => {} + check_expr_inner(cx, expr, op.node.into(), op.span); + }, + _ => {}, } } } -impl<'tcx> SuspiciousImpl { - fn check_expr_inner( - &mut self, - cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - binop: hir::BinOpKind, - span: Span, - ) { - if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop) +fn check_expr_inner<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, binop: hir::BinOpKind, span: Span) { + if let Some((binop_trait_lang, op_assign_trait_lang)) = binop_traits(binop) && let Some(binop_trait_id) = cx.tcx.lang_items().get(binop_trait_lang) && let Some(op_assign_trait_id) = cx.tcx.lang_items().get(op_assign_trait_lang) @@ -98,18 +91,17 @@ impl<'tcx> SuspiciousImpl { .iter() .find(|&(ts, _)| ts.iter().any(|&t| Some(trait_id) == cx.tcx.lang_items().get(t))) && count_binops(body.value) == 1 - { - span_lint( - cx, - lint, - span, - format!( - "suspicious use of `{}` in `{}` impl", - binop.as_str(), - cx.tcx.item_name(trait_id) - ), - ); - } + { + span_lint( + cx, + lint, + span, + format!( + "suspicious use of `{}` in `{}` impl", + binop.as_str(), + cx.tcx.item_name(trait_id) + ), + ); } } diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 0337b74b4b1..e3ecd6508bf 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -133,7 +133,7 @@ fn generate_swap_warning<'tcx>( applicability: &mut applicability, } .snippet_index_bindings(&[idx1, idx2, rhs1, rhs2]), - slice.maybe_par(), + slice.maybe_paren(), snippet_with_context(cx, idx1.span, ctxt, "..", &mut applicability).0, snippet_with_context(cx, idx2.span, ctxt, "..", &mut applicability).0, ), @@ -269,12 +269,11 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr< if let ExprKind::Assign(lhs, rhs, _) = expr.kind { return Some((ExprOrIdent::Expr(lhs), rhs)); } - } else if let StmtKind::Let(expr) = stmt.kind { - if let Some(rhs) = expr.init { - if let PatKind::Binding(_, _, ident_l, _) = expr.pat.kind { - return Some((ExprOrIdent::Ident(ident_l), rhs)); - } - } + } else if let StmtKind::Let(expr) = stmt.kind + && let Some(rhs) = expr.init + && let PatKind::Binding(_, _, ident_l, _) = expr.pat.kind + { + return Some((ExprOrIdent::Ident(ident_l), rhs)); } None } diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index fa36c9a21f6..8aac3a59102 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -151,20 +151,19 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { .iter() .filter_map(get_trait_info_from_bound) .for_each(|(trait_item_res, trait_item_segments, span)| { - if let Some(self_segments) = self_bounds_map.get(&trait_item_res) { - if SpanlessEq::new(cx) + if let Some(self_segments) = self_bounds_map.get(&trait_item_res) + && SpanlessEq::new(cx) .paths_by_resolution() .eq_path_segments(self_segments, trait_item_segments) - { - span_lint_and_help( - cx, - TRAIT_DUPLICATION_IN_BOUNDS, - span, - "this trait bound is already specified in trait declaration", - None, - "consider removing this trait bound", - ); - } + { + span_lint_and_help( + cx, + TRAIT_DUPLICATION_IN_BOUNDS, + span, + "this trait bound is already specified in trait declaration", + None, + "consider removing this trait bound", + ); } }); } diff --git a/clippy_lints/src/transmute/transmute_float_to_int.rs b/clippy_lints/src/transmute/transmute_float_to_int.rs index f2c757952af..df2f681a162 100644 --- a/clippy_lints/src/transmute/transmute_float_to_int.rs +++ b/clippy_lints/src/transmute/transmute_float_to_int.rs @@ -47,7 +47,7 @@ pub(super) fn check<'tcx>( } } - sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_par()).into()); + sugg = sugg::Sugg::NonParen(format!("{}.to_bits()", sugg.maybe_paren()).into()); // cast the result of `to_bits` if `to_ty` is signed sugg = if let ty::Int(int_ty) = to_ty.kind() { diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs index fcc763763bd..933e25fe98c 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ptr.rs @@ -33,7 +33,7 @@ pub(super) fn check<'tcx>( diag.span_suggestion_verbose( e.span, "use `pointer::cast` instead", - format!("{}.cast::<{to_pointee_ty}>()", arg.maybe_par()), + format!("{}.cast::<{to_pointee_ty}>()", arg.maybe_paren()), Applicability::MaybeIncorrect, ); } else if from_pointee_ty == to_pointee_ty @@ -48,7 +48,7 @@ pub(super) fn check<'tcx>( diag.span_suggestion_verbose( e.span, format!("use `pointer::{method}` instead"), - format!("{}.{method}()", arg.maybe_par()), + format!("{}.{method}()", arg.maybe_paren()), Applicability::MaybeIncorrect, ); } else { diff --git a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs index 45ee83c78ab..e58212fae15 100644 --- a/clippy_lints/src/transmute/transmute_ptr_to_ref.rs +++ b/clippy_lints/src/transmute/transmute_ptr_to_ref.rs @@ -38,7 +38,7 @@ pub(super) fn check<'tcx>( let sugg = if let Some(ty) = get_explicit_type(path) { let ty_snip = snippet_with_applicability(cx, ty.span, "..", &mut app); if msrv.meets(cx, msrvs::POINTER_CAST) { - format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_par()) + format!("{deref}{}.cast::<{ty_snip}>()", arg.maybe_paren()) } else if from_ptr_ty.has_erased_regions() { sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {ty_snip}"))).to_string() } else { @@ -47,7 +47,7 @@ pub(super) fn check<'tcx>( } else if *from_ptr_ty == *to_ref_ty { if from_ptr_ty.has_erased_regions() { if msrv.meets(cx, msrvs::POINTER_CAST) { - format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_par()) + format!("{deref}{}.cast::<{to_ref_ty}>()", arg.maybe_paren()) } else { sugg::make_unop(deref, arg.as_ty(format!("{cast} () as {cast} {to_ref_ty}"))) .to_string() diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index b6f4c4d7f0a..3147058b4cd 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -591,26 +591,26 @@ impl Types { TyKind::Path(ref qpath) if !context.in_body => { let hir_id = hir_ty.hir_id; let res = cx.qpath_res(qpath, hir_id); - if let Some(def_id) = res.opt_def_id() { - if self.is_type_change_allowed(context) { - // All lints that are being checked in this block are guarded by - // the `avoid_breaking_exported_api` configuration. When adding a - // new lint, please also add the name to the configuration documentation - // in `clippy_config::conf` - - let mut triggered = false; - triggered |= box_collection::check(cx, hir_ty, qpath, def_id); - triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id); - triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id); - triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold); - triggered |= option_option::check(cx, hir_ty, qpath, def_id); - triggered |= linked_list::check(cx, hir_ty, def_id); - triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id); - triggered |= owned_cow::check(cx, qpath, def_id); - - if triggered { - return; - } + if let Some(def_id) = res.opt_def_id() + && self.is_type_change_allowed(context) + { + // All lints that are being checked in this block are guarded by + // the `avoid_breaking_exported_api` configuration. When adding a + // new lint, please also add the name to the configuration documentation + // in `clippy_config::conf` + + let mut triggered = false; + triggered |= box_collection::check(cx, hir_ty, qpath, def_id); + triggered |= redundant_allocation::check(cx, hir_ty, qpath, def_id); + triggered |= rc_buffer::check(cx, hir_ty, qpath, def_id); + triggered |= vec_box::check(cx, hir_ty, qpath, def_id, self.vec_box_size_threshold); + triggered |= option_option::check(cx, hir_ty, qpath, def_id); + triggered |= linked_list::check(cx, hir_ty, def_id); + triggered |= rc_mutex::check(cx, hir_ty, qpath, def_id); + triggered |= owned_cow::check(cx, qpath, def_id); + + if triggered { + return; } } match *qpath { diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 0c17cc5d8f6..d321c48f6af 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -23,8 +23,8 @@ declare_clippy_lint! { /// implementations. /// /// ### Why is this bad? - /// This is a hard to find infinite recursion that will crash any code - /// using it. + /// Infinite recursion in trait implementation will either cause crashes + /// or result in an infinite loop, and it is hard to detect. /// /// ### Example /// ```no_run @@ -39,9 +39,31 @@ declare_clippy_lint! { /// } /// } /// ``` + /// /// Use instead: /// - /// In such cases, either use `#[derive(PartialEq)]` or don't implement it. + /// ```no_run + /// #[derive(PartialEq)] + /// enum Foo { + /// A, + /// B, + /// } + /// ``` + /// + /// As an alternative, rewrite the logic without recursion: + /// + /// ```no_run + /// enum Foo { + /// A, + /// B, + /// } + /// + /// impl PartialEq for Foo { + /// fn eq(&self, other: &Self) -> bool { + /// matches!((self, other), (Foo::A, Foo::A) | (Foo::B, Foo::B)) + /// } + /// } + /// ``` #[clippy::version = "1.77.0"] pub UNCONDITIONAL_RECURSION, suspicious, @@ -113,7 +135,7 @@ fn get_impl_trait_def_id(cx: &LateContext<'_>, method_def_id: LocalDefId) -> Opt }), )) = cx.tcx.hir_parent_iter(hir_id).next() // We exclude `impl` blocks generated from rustc's proc macros. - && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + && !cx.tcx.is_automatically_derived(owner_id.to_def_id()) // It is a implementation of a trait. && let Some(trait_) = impl_.of_trait { @@ -218,7 +240,7 @@ fn check_to_string(cx: &LateContext<'_>, method_span: Span, method_def_id: Local }), )) = cx.tcx.hir_parent_iter(hir_id).next() // We exclude `impl` blocks generated from rustc's proc macros. - && !cx.tcx.has_attr(*owner_id, sym::automatically_derived) + && !cx.tcx.is_automatically_derived(owner_id.to_def_id()) // It is a implementation of a trait. && let Some(trait_) = impl_.of_trait && let Some(trait_def_id) = trait_.trait_def_id() @@ -315,7 +337,7 @@ impl UnconditionalRecursion { for (ty, impl_def_ids) in impls.non_blanket_impls() { let Some(self_def_id) = ty.def() else { continue }; for impl_def_id in impl_def_ids { - if !cx.tcx.has_attr(*impl_def_id, sym::automatically_derived) && + if !cx.tcx.is_automatically_derived(*impl_def_id) && let Some(assoc_item) = cx .tcx .associated_items(impl_def_id) diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index e1fc644e4ce..79571b0409d 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -76,10 +76,10 @@ declare_lint_pass!(Unicode => [INVISIBLE_CHARACTERS, NON_ASCII_LITERAL, UNICODE_ impl LateLintPass<'_> for Unicode { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { - if let ExprKind::Lit(lit) = expr.kind { - if let LitKind::Str(_, _) | LitKind::Char(_) = lit.node { - check_str(cx, lit.span, expr.hir_id); - } + if let ExprKind::Lit(lit) = expr.kind + && let LitKind::Str(_, _) | LitKind::Char(_) = lit.node + { + check_str(cx, lit.span, expr.hir_id); } } } diff --git a/clippy_lints/src/unnecessary_wraps.rs b/clippy_lints/src/unnecessary_wraps.rs index 937e35dea96..bcd05cceca9 100644 --- a/clippy_lints/src/unnecessary_wraps.rs +++ b/clippy_lints/src/unnecessary_wraps.rs @@ -93,13 +93,13 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { // Abort if the method is implementing a trait or of it a trait method. let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); - if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { - if matches!( + if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) + && matches!( item.kind, ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) - ) { - return; - } + ) + { + return; } // Get the wrapper and inner types, if can't, abort. diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 8966e6851ac..9ad184450de 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -69,10 +69,10 @@ impl EarlyLintPass for UnnestedOrPatterns { } fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) { - if self.msrv.meets(msrvs::OR_PATTERNS) { - if let ast::ExprKind::Let(pat, _, _, _) = &e.kind { - lint_unnested_or_patterns(cx, pat); - } + if self.msrv.meets(msrvs::OR_PATTERNS) + && let ast::ExprKind::Let(pat, _, _, _) = &e.kind + { + lint_unnested_or_patterns(cx, pat); } } @@ -120,18 +120,25 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { /// Remove all `(p)` patterns in `pat`. fn remove_all_parens(pat: &mut P<Pat>) { - struct Visitor; + #[derive(Default)] + struct Visitor { + /// If is not in the outer most pattern. This is needed to avoid removing the outermost + /// parens because top-level or-patterns are not allowed in let statements. + is_inner: bool, + } + impl MutVisitor for Visitor { fn visit_pat(&mut self, pat: &mut P<Pat>) { + let is_inner = mem::replace(&mut self.is_inner, true); walk_pat(self, pat); let inner = match &mut pat.kind { - Paren(i) => mem::replace(&mut i.kind, Wild), + Paren(i) if is_inner => mem::replace(&mut i.kind, Wild), _ => return, }; pat.kind = inner; } } - Visitor.visit_pat(pat); + Visitor::default().visit_pat(pat); } /// Insert parens where necessary according to Rust's precedence rules for patterns. diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 0687fc319af..2d88c490b1a 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -265,15 +265,14 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { /// If `expr` is an (e).await, return the inner expression "e" that's being /// 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, [arg_0]) = expr.kind { - if matches!( - func.kind, - ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) - ) { - return arg_0; - } - } + if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind + && let ExprKind::Call(func, [arg_0]) = expr.kind + && matches!( + func.kind, + ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) + ) + { + return arg_0; } expr } diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index b466a8e127a..ce82b56eb94 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::usage::is_potentially_local_place; -use clippy_utils::{higher, path_to_local}; +use clippy_utils::{higher, path_to_local, sym}; use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, PathSegment, UnOp}; @@ -11,8 +11,8 @@ use rustc_middle::hir::nested_filter; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::declare_lint_pass; +use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -307,8 +307,8 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind && let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg) && let Some(id) = path_to_local(self_arg) - && [sym::unwrap, sym::expect, sym!(unwrap_err)].contains(&method_name.ident.name) - && let call_to_unwrap = [sym::unwrap, sym::expect].contains(&method_name.ident.name) + && matches!(method_name.ident.name, sym::unwrap | sym::expect | sym::unwrap_err) + && let call_to_unwrap = matches!(method_name.ident.name, sym::unwrap | sym::expect) && let Some(unwrappable) = self.unwrappables.iter() .find(|u| u.local_id == id) // Span contexts should not differ with the conditional branch diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 57bb2fc27f1..3a9c997a579 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -92,36 +92,36 @@ fn into_iter_bound<'tcx>( let mut into_iter_span = None; for (pred, span) in cx.tcx.explicit_predicates_of(fn_did).predicates { - if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() { - if tr.self_ty().is_param(param_index) { - if tr.def_id() == into_iter_did { - into_iter_span = Some(*span); - } else { - let tr = cx.tcx.erase_regions(tr); - if tr.has_escaping_bound_vars() { - return None; - } - - // Substitute generics in the predicate and replace the IntoIterator type parameter with the - // `.into_iter()` receiver to see if the bound also holds for that type. - let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| { - if i == param_index as usize { - GenericArg::from(into_iter_receiver) - } else { - arg - } - })); + if let ty::ClauseKind::Trait(tr) = pred.kind().skip_binder() + && tr.self_ty().is_param(param_index) + { + if tr.def_id() == into_iter_did { + into_iter_span = Some(*span); + } else { + let tr = cx.tcx.erase_regions(tr); + if tr.has_escaping_bound_vars() { + return None; + } - let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args); - let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); - if !cx - .tcx - .infer_ctxt() - .build(cx.typing_mode()) - .predicate_must_hold_modulo_regions(&obligation) - { - return None; + // Substitute generics in the predicate and replace the IntoIterator type parameter with the + // `.into_iter()` receiver to see if the bound also holds for that type. + let args = cx.tcx.mk_args_from_iter(node_args.iter().enumerate().map(|(i, arg)| { + if i == param_index as usize { + GenericArg::from(into_iter_receiver) + } else { + arg } + })); + + let predicate = EarlyBinder::bind(tr).instantiate(cx.tcx, args); + let obligation = Obligation::new(cx.tcx, ObligationCause::dummy(), cx.param_env, predicate); + if !cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .predicate_must_hold_modulo_regions(&obligation) + { + return None; } } } @@ -356,7 +356,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if cx.tcx.is_diagnostic_item(sym::from_fn, def_id) && same_type_and_consts(a, b) { let mut app = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_par(); + let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); span_lint_and_sugg( cx, diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs deleted file mode 100644 index deb983b6971..00000000000 --- a/clippy_lints/src/utils/internal_lints.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod almost_standard_lint_formulation; -pub mod collapsible_calls; -pub mod interning_defined_symbol; -pub mod invalid_paths; -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/almost_standard_lint_formulation.rs b/clippy_lints/src/utils/internal_lints/almost_standard_lint_formulation.rs deleted file mode 100644 index 0a01a364a75..00000000000 --- a/clippy_lints/src/utils/internal_lints/almost_standard_lint_formulation.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::utils::internal_lints::lint_without_lint_pass::is_lint_ref_type; -use clippy_utils::diagnostics::span_lint_and_help; -use regex::Regex; -use rustc_hir::{Attribute, Item, ItemKind, Mutability}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks if lint formulations have a standardized format. - /// - /// ### Why is this bad? - /// It's not necessarily bad, but we try to enforce a standard in Clippy. - /// - /// ### Example - /// `Checks for use...` can be written as `Checks for usage...` . - pub ALMOST_STANDARD_LINT_FORMULATION, - internal, - "lint formulations must have a standardized format." -} - -impl_lint_pass!(AlmostStandardFormulation => [ALMOST_STANDARD_LINT_FORMULATION]); - -pub struct AlmostStandardFormulation { - standard_formulations: Vec<StandardFormulations<'static>>, -} - -#[derive(Debug)] -struct StandardFormulations<'a> { - wrong_pattern: Regex, - correction: &'a str, -} - -impl AlmostStandardFormulation { - pub fn new() -> Self { - let standard_formulations = vec![StandardFormulations { - wrong_pattern: Regex::new("^(Check for|Detects? uses?)").unwrap(), - correction: "Checks for", - }]; - Self { standard_formulations } - } -} - -impl<'tcx> LateLintPass<'tcx> for AlmostStandardFormulation { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - let mut check_next = false; - if let ItemKind::Static(_, ty, Mutability::Not, _) = item.kind { - let lines = cx - .tcx - .hir_attrs(item.hir_id()) - .iter() - .filter_map(|attr| Attribute::doc_str(attr).map(|sym| (sym, attr))); - if is_lint_ref_type(cx, ty) { - for (line, attr) in lines { - let cur_line = line.as_str().trim(); - if check_next && !cur_line.is_empty() { - for formulation in &self.standard_formulations { - let starts_with_correct_formulation = cur_line.starts_with(formulation.correction); - if !starts_with_correct_formulation && formulation.wrong_pattern.is_match(cur_line) { - if let Some(ident) = attr.ident() { - span_lint_and_help( - cx, - ALMOST_STANDARD_LINT_FORMULATION, - ident.span, - "non-standard lint formulation", - None, - format!("consider using `{}`", formulation.correction), - ); - } - return; - } - } - return; - } else if cur_line.contains("What it does") { - check_next = true; - } else if cur_line.contains("Why is this bad") { - // Formulation documentation is done. Can add check to ensure that missing formulation is added - // and add a check if it matches no accepted formulation - return; - } - } - } - } - } -} diff --git a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs deleted file mode 100644 index 2e6fb7c4ce4..00000000000 --- a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs +++ /dev/null @@ -1,248 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::{SpanlessEq, is_expr_path_def_path, is_lint_allowed, peel_blocks_with_stmt}; -use rustc_errors::Applicability; -use rustc_hir::{Closure, Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; - -use std::borrow::{Borrow, Cow}; - -declare_clippy_lint! { - /// ### What it does - /// Lints `span_lint_and_then` function calls, where the - /// closure argument has only one statement and that statement is a method - /// call to `span_suggestion`, `span_help`, `span_note` (using the same - /// span), `help` or `note`. - /// - /// These usages of `span_lint_and_then` should be replaced with one of the - /// wrapper functions `span_lint_and_sugg`, span_lint_and_help`, or - /// `span_lint_and_note`. - /// - /// ### Why is this bad? - /// Using the wrapper `span_lint_and_*` functions, is more - /// convenient, readable and less error prone. - /// - /// ### Example - /// ```rust,ignore - /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { - /// diag.span_suggestion( - /// expr.span, - /// help_msg, - /// sugg.to_string(), - /// Applicability::MachineApplicable, - /// ); - /// }); - /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { - /// diag.span_help(expr.span, help_msg); - /// }); - /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { - /// diag.help(help_msg); - /// }); - /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { - /// diag.span_note(expr.span, note_msg); - /// }); - /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { - /// diag.note(note_msg); - /// }); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// span_lint_and_sugg( - /// cx, - /// TEST_LINT, - /// expr.span, - /// lint_msg, - /// help_msg, - /// sugg.to_string(), - /// Applicability::MachineApplicable, - /// ); - /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), help_msg); - /// span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, help_msg); - /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), note_msg); - /// span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg); - /// ``` - pub COLLAPSIBLE_SPAN_LINT_CALLS, - internal, - "found collapsible `span_lint_and_then` calls" -} - -declare_lint_pass!(CollapsibleCalls => [COLLAPSIBLE_SPAN_LINT_CALLS]); - -impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if is_lint_allowed(cx, COLLAPSIBLE_SPAN_LINT_CALLS, expr.hir_id) { - return; - } - - 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 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 - && let ExprKind::Path(..) = recv.kind - { - let and_then_snippets = - get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span); - let mut sle = SpanlessEq::new(cx).deny_side_effects(); - match ps.ident.as_str() { - "span_suggestion" if sle.eq_expr(call_sp, &span_call_args[0]) => { - suggest_suggestion( - cx, - expr, - &and_then_snippets, - &span_suggestion_snippets(cx, span_call_args), - ); - }, - "span_help" if sle.eq_expr(call_sp, &span_call_args[0]) => { - let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true); - }, - "span_note" if sle.eq_expr(call_sp, &span_call_args[0]) => { - let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true); - }, - "help" => { - let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#); - suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false); - }, - "note" => { - let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#); - suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false); - }, - _ => (), - } - } - } -} - -struct AndThenSnippets<'a> { - cx: Cow<'a, str>, - lint: Cow<'a, str>, - span: Cow<'a, str>, - msg: Cow<'a, str>, -} - -fn get_and_then_snippets( - cx: &LateContext<'_>, - cx_span: Span, - lint_span: Span, - span_span: Span, - msg_span: Span, -) -> AndThenSnippets<'static> { - let cx_snippet = snippet(cx, cx_span, "cx"); - let lint_snippet = snippet(cx, lint_span, ".."); - let span_snippet = snippet(cx, span_span, "span"); - let msg_snippet = snippet(cx, msg_span, r#""...""#); - - AndThenSnippets { - cx: cx_snippet, - lint: lint_snippet, - span: span_snippet, - msg: msg_snippet, - } -} - -struct SpanSuggestionSnippets<'a> { - help: Cow<'a, str>, - sugg: Cow<'a, str>, - applicability: Cow<'a, str>, -} - -fn span_suggestion_snippets<'a, 'hir>( - cx: &LateContext<'_>, - span_call_args: &'hir [Expr<'hir>], -) -> SpanSuggestionSnippets<'a> { - let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#); - let sugg_snippet = snippet(cx, span_call_args[2].span, ".."); - let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable"); - - SpanSuggestionSnippets { - help: help_snippet, - sugg: sugg_snippet, - applicability: applicability_snippet, - } -} - -fn suggest_suggestion( - cx: &LateContext<'_>, - expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, - span_suggestion_snippets: &SpanSuggestionSnippets<'_>, -) { - span_lint_and_sugg( - cx, - COLLAPSIBLE_SPAN_LINT_CALLS, - expr.span, - "this call is collapsible", - "collapse into", - format!( - "span_lint_and_sugg({}, {}, {}, {}, {}, {}, {})", - and_then_snippets.cx, - and_then_snippets.lint, - and_then_snippets.span, - and_then_snippets.msg, - span_suggestion_snippets.help, - span_suggestion_snippets.sugg, - span_suggestion_snippets.applicability - ), - Applicability::MachineApplicable, - ); -} - -fn suggest_help( - cx: &LateContext<'_>, - expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, - help: &str, - with_span: bool, -) { - let option_span = if with_span { - format!("Some({})", and_then_snippets.span) - } else { - "None".to_string() - }; - - span_lint_and_sugg( - cx, - COLLAPSIBLE_SPAN_LINT_CALLS, - expr.span, - "this call is collapsible", - "collapse into", - format!( - "span_lint_and_help({}, {}, {}, {}, {}, {help})", - and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span, - ), - Applicability::MachineApplicable, - ); -} - -fn suggest_note( - cx: &LateContext<'_>, - expr: &Expr<'_>, - and_then_snippets: &AndThenSnippets<'_>, - note: &str, - with_span: bool, -) { - let note_span = if with_span { - format!("Some({})", and_then_snippets.span) - } else { - "None".to_string() - }; - - span_lint_and_sugg( - cx, - COLLAPSIBLE_SPAN_LINT_CALLS, - expr.span, - "this call is collapsible", - "collapse into", - format!( - "span_lint_and_note({}, {}, {}, {}, {note_span}, {note})", - and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, - ), - Applicability::MachineApplicable, - ); -} diff --git a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs deleted file mode 100644 index e454427adde..00000000000 --- a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs +++ /dev/null @@ -1,241 +0,0 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::match_type; -use clippy_utils::{def_path_def_ids, is_expn_of, match_def_path, paths}; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::DefId; -use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::mir::ConstValue; -use rustc_middle::ty; -use rustc_session::impl_lint_pass; -use rustc_span::sym; -use rustc_span::symbol::Symbol; - -use std::borrow::Cow; - -declare_clippy_lint! { - /// ### What it does - /// Checks for interning symbols that have already been pre-interned and defined as constants. - /// - /// ### Why is this bad? - /// It's faster and easier to use the symbol constant. - /// - /// ### Example - /// ```rust,ignore - /// let _ = sym!(f32); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// let _ = sym::f32; - /// ``` - pub INTERNING_DEFINED_SYMBOL, - internal, - "interning a symbol that is pre-interned and defined as a constant" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for unnecessary conversion from Symbol to a string. - /// - /// ### Why is this bad? - /// It's faster use symbols directly instead of strings. - /// - /// ### Example - /// ```rust,ignore - /// symbol.as_str() == "clippy"; - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// symbol == sym::clippy; - /// ``` - pub UNNECESSARY_SYMBOL_STR, - internal, - "unnecessary conversion between Symbol and string" -} - -#[derive(Default)] -pub struct InterningDefinedSymbol { - // Maps the symbol value to the constant DefId. - symbol_map: FxHashMap<u32, DefId>, -} - -impl_lint_pass!(InterningDefinedSymbol => [INTERNING_DEFINED_SYMBOL, UNNECESSARY_SYMBOL_STR]); - -impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { - fn check_crate(&mut self, cx: &LateContext<'_>) { - if !self.symbol_map.is_empty() { - return; - } - - for &module in &[&paths::KW_MODULE, &paths::SYM_MODULE] { - for def_id in def_path_def_ids(cx.tcx, module) { - for item in cx.tcx.module_children(def_id) { - if let Res::Def(DefKind::Const, item_def_id) = item.res - && let ty = cx.tcx.type_of(item_def_id).instantiate_identity() - && match_type(cx, ty, &paths::SYMBOL) - && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id) - && let Some(value) = value.to_u32().discard_err() - { - self.symbol_map.insert(value, item_def_id); - } - } - } - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Call(func, [arg]) = &expr.kind - && let ty::FnDef(def_id, _) = cx.typeck_results().expr_ty(func).kind() - && cx.tcx.is_diagnostic_item(sym::SymbolIntern, *def_id) - && let Some(Constant::Str(arg)) = ConstEvalCtxt::new(cx).eval_simple(arg) - && let value = Symbol::intern(&arg).as_u32() - && let Some(&def_id) = self.symbol_map.get(&value) - { - span_lint_and_sugg( - cx, - INTERNING_DEFINED_SYMBOL, - is_expn_of(expr.span, "sym").unwrap_or(expr.span), - "interning a defined symbol", - "try", - cx.tcx.def_path_str(def_id), - Applicability::MachineApplicable, - ); - } - if let ExprKind::Binary(op, left, right) = expr.kind { - if matches!(op.node, BinOpKind::Eq | BinOpKind::Ne) { - let data = [ - (left, self.symbol_str_expr(left, cx)), - (right, self.symbol_str_expr(right, cx)), - ]; - match data { - // both operands are a symbol string - [(_, Some(left)), (_, Some(right))] => { - span_lint_and_sugg( - cx, - UNNECESSARY_SYMBOL_STR, - expr.span, - "unnecessary `Symbol` to string conversion", - "try", - format!( - "{} {} {}", - left.as_symbol_snippet(cx), - op.node.as_str(), - right.as_symbol_snippet(cx), - ), - Applicability::MachineApplicable, - ); - }, - // one of the operands is a symbol string - [(expr, Some(symbol)), _] | [_, (expr, Some(symbol))] => { - // creating an owned string for comparison - if matches!(symbol, SymbolStrExpr::Expr { is_to_owned: true, .. }) { - span_lint_and_sugg( - cx, - UNNECESSARY_SYMBOL_STR, - expr.span, - "unnecessary string allocation", - "try", - format!("{}.as_str()", symbol.as_symbol_snippet(cx)), - Applicability::MachineApplicable, - ); - } - }, - // nothing found - [(_, None), (_, None)] => {}, - } - } - } - } -} - -impl InterningDefinedSymbol { - fn symbol_str_expr<'tcx>(&self, expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> Option<SymbolStrExpr<'tcx>> { - static IDENT_STR_PATHS: &[&[&str]] = &[&paths::IDENT_AS_STR]; - static SYMBOL_STR_PATHS: &[&[&str]] = &[&paths::SYMBOL_AS_STR, &paths::SYMBOL_TO_IDENT_STRING]; - let call = if let ExprKind::AddrOf(_, _, e) = expr.kind - && let ExprKind::Unary(UnOp::Deref, e) = e.kind - { - e - } else { - expr - }; - if let ExprKind::MethodCall(_, item, [], _) = call.kind - // is a method call - && let Some(did) = cx.typeck_results().type_dependent_def_id(call.hir_id) - && let ty = cx.typeck_results().expr_ty(item) - // ...on either an Ident or a Symbol - && let Some(is_ident) = if match_type(cx, ty, &paths::SYMBOL) { - Some(false) - } else if match_type(cx, ty, &paths::IDENT) { - Some(true) - } else { - None - } - // ...which converts it to a string - && let paths = if is_ident { IDENT_STR_PATHS } else { SYMBOL_STR_PATHS } - && let Some(is_to_owned) = paths - .iter() - .find_map(|path| if match_def_path(cx, did, path) { - Some(path == &paths::SYMBOL_TO_IDENT_STRING) - } else { - None - }) - .or_else(|| if cx.tcx.is_diagnostic_item(sym::to_string_method, did) { - Some(true) - } else { - None - }) - { - return Some(SymbolStrExpr::Expr { - item, - is_ident, - is_to_owned, - }); - } - // is a string constant - if let Some(Constant::Str(s)) = ConstEvalCtxt::new(cx).eval_simple(expr) { - let value = Symbol::intern(&s).as_u32(); - // ...which matches a symbol constant - if let Some(&def_id) = self.symbol_map.get(&value) { - return Some(SymbolStrExpr::Const(def_id)); - } - } - None - } -} - -enum SymbolStrExpr<'tcx> { - /// a string constant with a corresponding symbol constant - Const(DefId), - /// a "symbol to string" expression like `symbol.as_str()` - Expr { - /// part that evaluates to `Symbol` or `Ident` - item: &'tcx Expr<'tcx>, - is_ident: bool, - /// whether an owned `String` is created like `to_ident_string()` - is_to_owned: bool, - }, -} - -impl<'tcx> SymbolStrExpr<'tcx> { - /// Returns a snippet that evaluates to a `Symbol` and is const if possible - fn as_symbol_snippet(&self, cx: &LateContext<'_>) -> Cow<'tcx, str> { - match *self { - Self::Const(def_id) => cx.tcx.def_path_str(def_id).into(), - Self::Expr { item, is_ident, .. } => { - let mut snip = snippet(cx, item.span.source_callsite(), ".."); - if is_ident { - // get `Ident.name` - snip.to_mut().push_str(".name"); - } - snip - }, - } - } -} diff --git a/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/clippy_lints/src/utils/internal_lints/invalid_paths.rs deleted file mode 100644 index 252ac5e6768..00000000000 --- a/clippy_lints/src/utils/internal_lints/invalid_paths.rs +++ /dev/null @@ -1,106 +0,0 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::def_path_res; -use clippy_utils::diagnostics::span_lint; -use rustc_hir as hir; -use rustc_hir::Item; -use rustc_hir::def::DefKind; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::fast_reject::SimplifiedType; -use rustc_middle::ty::{self, FloatTy}; -use rustc_session::declare_lint_pass; -use rustc_span::symbol::Symbol; - -declare_clippy_lint! { - /// ### What it does - /// Checks the paths module for invalid paths. - /// - /// ### Why is this bad? - /// It indicates a bug in the code. - /// - /// ### Example - /// None. - pub INVALID_PATHS, - internal, - "invalid path" -} - -declare_lint_pass!(InvalidPaths => [INVALID_PATHS]); - -impl<'tcx> LateLintPass<'tcx> for InvalidPaths { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - let local_def_id = &cx.tcx.parent_module(item.hir_id()); - let mod_name = &cx.tcx.item_name(local_def_id.to_def_id()); - if mod_name.as_str() == "paths" - && let hir::ItemKind::Const(.., body_id) = item.kind - && let Some(Constant::Vec(path)) = ConstEvalCtxt::with_env( - cx.tcx, - ty::TypingEnv::post_analysis(cx.tcx, item.owner_id), - cx.tcx.typeck(item.owner_id), - ) - .eval_simple(cx.tcx.hir_body(body_id).value) - && let Some(path) = path - .iter() - .map(|x| { - if let Constant::Str(s) = x { - Some(s.as_str()) - } else { - None - } - }) - .collect::<Option<Vec<&str>>>() - && !check_path(cx, &path[..]) - { - span_lint(cx, INVALID_PATHS, item.span, "invalid path"); - } - } -} - -// This is not a complete resolver for paths. It works on all the paths currently used in the paths -// module. That's all it does and all it needs to do. -pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { - if !def_path_res(cx.tcx, path).is_empty() { - return true; - } - - // Some implementations can't be found by `path_to_res`, particularly inherent - // implementations of native types. Check lang items. - let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect(); - let lang_items = cx.tcx.lang_items(); - // This list isn't complete, but good enough for our current list of paths. - let incoherent_impls = [ - SimplifiedType::Float(FloatTy::F32), - SimplifiedType::Float(FloatTy::F64), - SimplifiedType::Slice, - SimplifiedType::Str, - SimplifiedType::Bool, - SimplifiedType::Char, - ] - .iter() - .flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter()) - .copied(); - for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) { - let lang_item_path = cx.get_def_path(item_def_id); - if path_syms.starts_with(&lang_item_path) { - if let [item] = &path_syms[lang_item_path.len()..] { - if matches!( - cx.tcx.def_kind(item_def_id), - DefKind::Mod | DefKind::Enum | DefKind::Trait - ) { - for child in cx.tcx.module_children(item_def_id) { - if child.ident.name == *item { - return true; - } - } - } else { - for child in cx.tcx.associated_item_def_ids(item_def_id) { - if cx.tcx.item_name(*child) == *item { - return true; - } - } - } - } - } - } - - false -} 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 deleted file mode 100644 index 94a2e598522..00000000000 --- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ /dev/null @@ -1,283 +0,0 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; -use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::{is_lint_allowed, match_def_path, paths}; -use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; -use rustc_hir as hir; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::hir_id::CRATE_HIR_ID; -use rustc_hir::intravisit::Visitor; -use rustc_hir::{ExprKind, HirId, Item, MutTy, Mutability, Path, TyKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::hir::nested_filter; -use rustc_session::impl_lint_pass; -use rustc_span::source_map::Spanned; -use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; - -declare_clippy_lint! { - /// ### What it does - /// Ensures every lint is associated to a `LintPass`. - /// - /// ### Why is this bad? - /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not - /// know the name of the lint. - /// - /// ### Known problems - /// Only checks for lints associated using the `declare_lint_pass!` and - /// `impl_lint_pass!` macros. - /// - /// ### Example - /// ```rust,ignore - /// declare_lint! { pub LINT_1, ... } - /// declare_lint! { pub LINT_2, ... } - /// declare_lint! { pub FORGOTTEN_LINT, ... } - /// // ... - /// declare_lint_pass!(Pass => [LINT_1, LINT_2]); - /// // missing FORGOTTEN_LINT - /// ``` - pub LINT_WITHOUT_LINT_PASS, - internal, - "declaring a lint without associating it in a LintPass" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for cases of an auto-generated lint without an updated description, - /// i.e. `default lint description`. - /// - /// ### Why is this bad? - /// Indicates that the lint is not finished. - /// - /// ### Example - /// ```rust,ignore - /// declare_lint! { pub COOL_LINT, nursery, "default lint description" } - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" } - /// ``` - pub DEFAULT_LINT, - internal, - "found 'default lint description' in a lint declaration" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for invalid `clippy::version` attributes. - /// - /// Valid values are: - /// * "pre 1.29.0" - /// * any valid semantic version - pub INVALID_CLIPPY_VERSION_ATTRIBUTE, - internal, - "found an invalid `clippy::version` attribute" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for declared clippy lints without the `clippy::version` attribute. - /// - pub MISSING_CLIPPY_VERSION_ATTRIBUTE, - internal, - "found clippy lint without `clippy::version` attribute" -} - -#[derive(Clone, Debug, Default)] -pub struct LintWithoutLintPass { - declared_lints: FxIndexMap<Symbol, Span>, - registered_lints: FxIndexSet<Symbol>, -} - -impl_lint_pass!(LintWithoutLintPass => [ - DEFAULT_LINT, - LINT_WITHOUT_LINT_PASS, - INVALID_CLIPPY_VERSION_ATTRIBUTE, - MISSING_CLIPPY_VERSION_ATTRIBUTE, -]); - -impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { - fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if is_lint_allowed(cx, DEFAULT_LINT, item.hir_id()) { - return; - } - - if let hir::ItemKind::Static(ident, ty, Mutability::Not, body_id) = item.kind { - if is_lint_ref_type(cx, ty) { - check_invalid_clippy_version_attribute(cx, item); - - let expr = &cx.tcx.hir_body(body_id).value; - let fields = if let ExprKind::AddrOf(_, _, inner_exp) = expr.kind - && let ExprKind::Struct(_, struct_fields, _) = inner_exp.kind - { - struct_fields - } else { - return; - }; - - let field = fields - .iter() - .find(|f| f.ident.as_str() == "desc") - .expect("lints must have a description field"); - - if let ExprKind::Lit(Spanned { - node: LitKind::Str(sym, _), - .. - }) = field.expr.kind - { - let sym_str = sym.as_str(); - if sym_str == "default lint description" { - span_lint( - cx, - DEFAULT_LINT, - item.span, - format!("the lint `{}` has the default lint description", ident.name), - ); - } - self.declared_lints.insert(ident.name, item.span); - } - } - } else if let Some(macro_call) = root_macro_call_first_node(cx, item) { - if !matches!( - cx.tcx.item_name(macro_call.def_id).as_str(), - "impl_lint_pass" | "declare_lint_pass" - ) { - return; - } - if let hir::ItemKind::Impl(hir::Impl { - of_trait: None, - items: impl_item_refs, - .. - }) = item.kind - { - let mut collector = LintCollector { - output: &mut self.registered_lints, - cx, - }; - let body = cx.tcx.hir_body_owned_by( - impl_item_refs - .iter() - .find(|iiref| iiref.ident.as_str() == "lint_vec") - .expect("LintPass needs to implement lint_vec") - .id - .owner_id - .def_id, - ); - collector.visit_expr(body.value); - } - } - } - - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - if is_lint_allowed(cx, LINT_WITHOUT_LINT_PASS, CRATE_HIR_ID) { - return; - } - - for (lint_name, &lint_span) in &self.declared_lints { - // When using the `declare_tool_lint!` macro, the original `lint_span`'s - // file points to "<rustc macros>". - // `compiletest-rs` thinks that's an error in a different file and - // just ignores it. This causes the test in compile-fail/lint_pass - // not able to capture the error. - // Therefore, we need to climb the macro expansion tree and find the - // actual span that invoked `declare_tool_lint!`: - let lint_span = lint_span.ctxt().outer_expn_data().call_site; - - if !self.registered_lints.contains(lint_name) { - span_lint( - cx, - LINT_WITHOUT_LINT_PASS, - lint_span, - format!("the lint `{lint_name}` is not added to any `LintPass`"), - ); - } - } - } -} - -pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { - if let TyKind::Ref( - _, - MutTy { - ty: inner, - mutbl: Mutability::Not, - }, - ) = ty.kind - { - if let TyKind::Path(ref path) = inner.kind { - if let Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, inner.hir_id) { - return match_def_path(cx, def_id, &paths::LINT); - } - } - } - - false -} - -fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) { - if let Some(value) = extract_clippy_version_value(cx, item) { - if value.as_str() == "pre 1.29.0" { - return; - } - - if rustc_attr_parsing::parse_version(value).is_none() { - span_lint_and_help( - cx, - INVALID_CLIPPY_VERSION_ATTRIBUTE, - item.span, - "this item has an invalid `clippy::version` attribute", - None, - "please use a valid semantic version, see `doc/adding_lints.md`", - ); - } - } else { - span_lint_and_help( - cx, - MISSING_CLIPPY_VERSION_ATTRIBUTE, - item.span, - "this lint is missing the `clippy::version` attribute or version value", - None, - "please use a `clippy::version` attribute, see `doc/adding_lints.md`", - ); - } -} - -/// This function extracts the version value of a `clippy::version` attribute if the given value has -/// one -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 hir::Attribute::Unparsed(attr_kind) = &attr - // Identify attribute - && let [tool_name, attr_name] = &attr_kind.path.segments[..] - && tool_name.name == sym::clippy - && attr_name.name == sym::version - && let Some(version) = attr.value_str() - { - Some(version) - } else { - None - } - }) -} - -struct LintCollector<'a, 'tcx> { - output: &'a mut FxIndexSet<Symbol>, - cx: &'a LateContext<'tcx>, -} - -impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> { - type NestedFilter = nested_filter::All; - - fn visit_path(&mut self, path: &Path<'_>, _: HirId) { - if path.segments.len() == 1 { - self.output.insert(path.segments[0].ident.name); - } - } - - fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt { - self.cx.tcx - } -} diff --git a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs deleted file mode 100644 index 558acacb972..00000000000 --- a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs +++ /dev/null @@ -1,58 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::match_type; -use clippy_utils::{match_def_path, paths}; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::ty::{self, EarlyBinder, GenericArgKind}; -use rustc_session::declare_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Check that the `extract_msrv_attr!` macro is used, when a lint has a MSRV. - /// - pub MISSING_MSRV_ATTR_IMPL, - internal, - "checking if all necessary steps were taken when adding a MSRV to a lint" -} - -declare_lint_pass!(MsrvAttrImpl => [MISSING_MSRV_ATTR_IMPL]); - -impl LateLintPass<'_> for MsrvAttrImpl { - fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { - if let hir::ItemKind::Impl(hir::Impl { - of_trait: Some(_), - items, - .. - }) = &item.kind - && let Some(trait_ref) = cx - .tcx - .impl_trait_ref(item.owner_id) - .map(EarlyBinder::instantiate_identity) - && match_def_path(cx, trait_ref.def_id, &paths::EARLY_LINT_PASS) - && let ty::Adt(self_ty_def, _) = trait_ref.self_ty().kind() - && self_ty_def.is_struct() - && self_ty_def.all_fields().any(|f| { - cx.tcx - .type_of(f.did) - .instantiate_identity() - .walk() - .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_))) - .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV_STACK)) - }) - && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") - { - let span = cx.sess().source_map().span_through_char(item.span, '{'); - span_lint_and_sugg( - cx, - MISSING_MSRV_ATTR_IMPL, - span, - "`extract_msrv_attr!` macro missing from `EarlyLintPass` implementation", - "add `extract_msrv_attr!()` to the `EarlyLintPass` implementation", - format!("{}\n extract_msrv_attr!();", snippet(cx, span, "..")), - Applicability::MachineApplicable, - ); - } - } -} diff --git a/clippy_lints/src/utils/internal_lints/outer_expn_data_pass.rs b/clippy_lints/src/utils/internal_lints/outer_expn_data_pass.rs deleted file mode 100644 index 326e1721461..00000000000 --- a/clippy_lints/src/utils/internal_lints/outer_expn_data_pass.rs +++ /dev/null @@ -1,59 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::match_type; -use clippy_utils::{is_lint_allowed, method_calls, paths}; -use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::symbol::Symbol; - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `cx.outer().expn_data()` and suggests to use - /// the `cx.outer_expn_data()` - /// - /// ### Why is this bad? - /// `cx.outer_expn_data()` is faster and more concise. - /// - /// ### Example - /// ```rust,ignore - /// expr.span.ctxt().outer().expn_data() - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// expr.span.ctxt().outer_expn_data() - /// ``` - pub OUTER_EXPN_EXPN_DATA, - internal, - "using `cx.outer_expn().expn_data()` instead of `cx.outer_expn_data()`" -} - -declare_lint_pass!(OuterExpnDataPass => [OUTER_EXPN_EXPN_DATA]); - -impl<'tcx> LateLintPass<'tcx> for OuterExpnDataPass { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - if is_lint_allowed(cx, OUTER_EXPN_EXPN_DATA, expr.hir_id) { - return; - } - - let (method_names, arg_lists, spans) = method_calls(expr, 2); - let method_names: Vec<&str> = method_names.iter().map(Symbol::as_str).collect(); - if let ["expn_data", "outer_expn"] = method_names.as_slice() - && let (self_arg, args) = arg_lists[1] - && args.is_empty() - && let self_ty = cx.typeck_results().expr_ty(self_arg).peel_refs() - && match_type(cx, self_ty, &paths::SYNTAX_CONTEXT) - { - span_lint_and_sugg( - cx, - OUTER_EXPN_EXPN_DATA, - spans[1].with_hi(expr.span.hi()), - "usage of `outer_expn().expn_data()`", - "try", - "outer_expn_data()".to_string(), - Applicability::MachineApplicable, - ); - } - } -} diff --git a/clippy_lints/src/utils/internal_lints/produce_ice.rs b/clippy_lints/src/utils/internal_lints/produce_ice.rs deleted file mode 100644 index 0a07919d659..00000000000 --- a/clippy_lints/src/utils/internal_lints/produce_ice.rs +++ /dev/null @@ -1,41 +0,0 @@ -use rustc_ast::ast::NodeId; -use rustc_ast::visit::FnKind; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; - -declare_clippy_lint! { - /// ### What it does - /// Not an actual lint. This lint is only meant for testing our customized internal compiler - /// error message by calling `panic`. - /// - /// ### Why is this bad? - /// ICE in large quantities can damage your teeth - /// - /// ### Example - /// ```rust,ignore - /// 🍦🍦🍦🍦🍦 - /// ``` - pub PRODUCE_ICE, - internal, - "this message should not appear anywhere as we ICE before and don't emit the lint" -} - -declare_lint_pass!(ProduceIce => [PRODUCE_ICE]); - -impl EarlyLintPass for ProduceIce { - fn check_fn(&mut self, ctx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { - if is_trigger_fn(fn_kind) { - ctx.sess() - .dcx() - .span_delayed_bug(span, "Would you like some help with that?"); - } - } -} - -fn is_trigger_fn(fn_kind: FnKind<'_>) -> bool { - match fn_kind { - FnKind::Fn(_, ident, ..) => ident.name.as_str() == "it_looks_like_you_are_trying_to_kill_clippy", - FnKind::Closure(..) => false, - } -} diff --git a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs deleted file mode 100644 index b8bcb9b3756..00000000000 --- a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs +++ /dev/null @@ -1,74 +0,0 @@ -use clippy_utils::consts::{ConstEvalCtxt, Constant}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::paths; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::match_type; -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, sym}; - -declare_clippy_lint! { - /// ### What it does - /// - /// Detects symbol comparison using `Symbol::intern`. - /// - /// ### Why is this bad? - /// - /// Comparison via `Symbol::as_str()` is faster if the interned symbols are not reused. - /// - /// ### Example - /// - /// None, see suggestion. - pub SLOW_SYMBOL_COMPARISONS, - internal, - "detects slow comparisons 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 ExprKind::Call(fun, args) = op2.kind - && let ExprKind::Path(ref qpath) = fun.kind - && cx - .tcx - .is_diagnostic_item(sym::SymbolIntern, cx.qpath_res(qpath, fun.hir_id).opt_def_id()?) - && let [symbol_name_expr] = args - && 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/internal_lints/unnecessary_def_path.rs b/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs deleted file mode 100644 index 76b0a52621b..00000000000 --- a/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs +++ /dev/null @@ -1,301 +0,0 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; -use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{def_path_def_ids, is_lint_allowed, match_any_def_paths, peel_hir_expr_refs}; -use rustc_ast::ast::LitKind; -use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; -use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, ExprKind, LetStmt, Mutability, Node}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::mir::ConstValue; -use rustc_middle::mir::interpret::{Allocation, GlobalAlloc}; -use rustc_middle::ty::{self, Ty}; -use rustc_session::impl_lint_pass; -use rustc_span::Span; -use rustc_span::symbol::Symbol; - -use std::str; - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of def paths when a diagnostic item or a `LangItem` could be used. - /// - /// ### Why is this bad? - /// The path for an item is subject to change and is less efficient to look up than a - /// diagnostic item or a `LangItem`. - /// - /// ### Example - /// ```rust,ignore - /// utils::match_type(cx, ty, &paths::VEC) - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// utils::is_type_diagnostic_item(cx, ty, sym::Vec) - /// ``` - pub UNNECESSARY_DEF_PATH, - internal, - "using a def path when a diagnostic item or a `LangItem` is available" -} - -impl_lint_pass!(UnnecessaryDefPath => [UNNECESSARY_DEF_PATH]); - -#[derive(Default)] -pub struct UnnecessaryDefPath { - array_def_ids: FxIndexSet<(DefId, Span)>, - linted_def_ids: FxHashSet<DefId>, -} - -impl<'tcx> LateLintPass<'tcx> for UnnecessaryDefPath { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if is_lint_allowed(cx, UNNECESSARY_DEF_PATH, expr.hir_id) { - return; - } - - match expr.kind { - ExprKind::Call(func, args) => self.check_call(cx, func, args, expr.span), - ExprKind::Array(elements) => self.check_array(cx, elements, expr.span), - _ => {}, - } - } - - fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - for &(def_id, span) in &self.array_def_ids { - if self.linted_def_ids.contains(&def_id) { - continue; - } - - let (msg, sugg) = if let Some(sym) = cx.tcx.get_diagnostic_name(def_id) { - ("diagnostic item", format!("sym::{sym}")) - } else if let Some(sym) = get_lang_item_name(cx, def_id) { - ("language item", format!("LangItem::{sym}")) - } else { - continue; - }; - - span_lint_and_help( - cx, - UNNECESSARY_DEF_PATH, - span, - format!("hardcoded path to a {msg}"), - None, - format!("convert all references to use `{sugg}`"), - ); - } - } -} - -impl UnnecessaryDefPath { - #[allow(clippy::too_many_lines)] - fn check_call(&mut self, cx: &LateContext<'_>, func: &Expr<'_>, args: &[Expr<'_>], span: Span) { - enum Item { - LangItem(&'static str), - DiagnosticItem(Symbol), - } - static PATHS: &[&[&str]] = &[ - &["clippy_utils", "match_def_path"], - &["clippy_utils", "match_trait_method"], - &["clippy_utils", "ty", "match_type"], - &["clippy_utils", "is_expr_path_def_path"], - ]; - - if let [cx_arg, def_arg, args @ ..] = args - && let ExprKind::Path(path) = &func.kind - && let Some(id) = cx.qpath_res(path, func.hir_id).opt_def_id() - && let Some(which_path) = match_any_def_paths(cx, id, PATHS) - && let item_arg = if which_path == 4 { &args[1] } else { &args[0] } - // Extract the path to the matched type - && let Some(segments) = path_to_matched_type(cx, item_arg) - && let segments = segments.iter().map(|sym| &**sym).collect::<Vec<_>>() - && let Some(def_id) = def_path_def_ids(cx.tcx, &segments[..]).next() - { - // Check if the target item is a diagnostic item or LangItem. - #[rustfmt::skip] - let (msg, item) = if let Some(item_name) - = cx.tcx.diagnostic_items(def_id.krate).id_to_name.get(&def_id) - { - ( - "use of a def path to a diagnostic item", - Item::DiagnosticItem(*item_name), - ) - } else if let Some(item_name) = get_lang_item_name(cx, def_id) { - ( - "use of a def path to a `LangItem`", - Item::LangItem(item_name), - ) - } else { - return; - }; - - let has_ctor = match cx.tcx.def_kind(def_id) { - DefKind::Struct => { - let variant = cx.tcx.adt_def(def_id).non_enum_variant(); - variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public()) - }, - DefKind::Variant => { - let variant = cx.tcx.adt_def(cx.tcx.parent(def_id)).variant_with_id(def_id); - variant.ctor.is_some() && variant.fields.iter().all(|f| f.vis.is_public()) - }, - _ => false, - }; - - let mut app = Applicability::MachineApplicable; - let cx_snip = snippet_with_applicability(cx, cx_arg.span, "..", &mut app); - let def_snip = snippet_with_applicability(cx, def_arg.span, "..", &mut app); - let (sugg, with_note) = match (which_path, item) { - // match_def_path - (0, Item::DiagnosticItem(item)) => ( - format!("{cx_snip}.tcx.is_diagnostic_item(sym::{item}, {def_snip})"), - has_ctor, - ), - (0, Item::LangItem(item)) => ( - format!("{cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some({def_snip})"), - has_ctor, - ), - // match_trait_method - (1, Item::DiagnosticItem(item)) => { - (format!("is_trait_method({cx_snip}, {def_snip}, sym::{item})"), false) - }, - // match_type - (2, Item::DiagnosticItem(item)) => ( - format!("is_type_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"), - false, - ), - (2, Item::LangItem(item)) => ( - format!("is_type_lang_item({cx_snip}, {def_snip}, LangItem::{item})"), - false, - ), - // is_expr_path_def_path - (3, Item::DiagnosticItem(item)) if has_ctor => ( - format!("is_res_diag_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), sym::{item})",), - false, - ), - (3, Item::LangItem(item)) if has_ctor => ( - format!("is_res_lang_ctor({cx_snip}, path_res({cx_snip}, {def_snip}), LangItem::{item})",), - false, - ), - (3, Item::DiagnosticItem(item)) => ( - format!("is_path_diagnostic_item({cx_snip}, {def_snip}, sym::{item})"), - false, - ), - (3, Item::LangItem(item)) => ( - format!( - "path_res({cx_snip}, {def_snip}).opt_def_id()\ - .map_or(false, |id| {cx_snip}.tcx.lang_items().get(LangItem::{item}) == Some(id))", - ), - false, - ), - _ => return, - }; - - span_lint_and_then(cx, UNNECESSARY_DEF_PATH, span, msg, |diag| { - diag.span_suggestion(span, "try", sugg, app); - if with_note { - diag.help( - "if this `DefId` came from a constructor expression or pattern then the \ - parent `DefId` should be used instead", - ); - } - }); - - self.linted_def_ids.insert(def_id); - } - } - - fn check_array(&mut self, cx: &LateContext<'_>, elements: &[Expr<'_>], span: Span) { - let Some(path) = path_from_array(elements) else { return }; - - for def_id in def_path_def_ids(cx.tcx, &path.iter().map(AsRef::as_ref).collect::<Vec<_>>()) { - self.array_def_ids.insert((def_id, span)); - } - } -} - -fn path_to_matched_type(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Vec<String>> { - match peel_hir_expr_refs(expr).0.kind { - ExprKind::Path(ref qpath) => match cx.qpath_res(qpath, expr.hir_id) { - Res::Local(hir_id) => { - if let Node::LetStmt(LetStmt { init: Some(init), .. }) = cx.tcx.parent_hir_node(hir_id) { - path_to_matched_type(cx, init) - } else { - None - } - }, - Res::Def(DefKind::Static { .. }, def_id) => read_mir_alloc_def_path( - cx, - cx.tcx.eval_static_initializer(def_id).ok()?.inner(), - cx.tcx.type_of(def_id).instantiate_identity(), - ), - Res::Def(DefKind::Const, def_id) => match cx.tcx.const_eval_poly(def_id).ok()? { - ConstValue::Indirect { alloc_id, offset } if offset.bytes() == 0 => { - let alloc = cx.tcx.global_alloc(alloc_id).unwrap_memory(); - read_mir_alloc_def_path(cx, alloc.inner(), cx.tcx.type_of(def_id).instantiate_identity()) - }, - _ => None, - }, - _ => None, - }, - ExprKind::Array(exprs) => path_from_array(exprs), - _ => None, - } -} - -fn read_mir_alloc_def_path<'tcx>(cx: &LateContext<'tcx>, alloc: &'tcx Allocation, ty: Ty<'_>) -> Option<Vec<String>> { - let (alloc, ty) = if let ty::Ref(_, ty, Mutability::Not) = *ty.kind() { - let &alloc = alloc.provenance().ptrs().values().next()?; - if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) { - (alloc.inner(), ty) - } else { - return None; - } - } else { - (alloc, ty) - }; - - if let ty::Array(ty, _) | ty::Slice(ty) = *ty.kind() - && let ty::Ref(_, ty, Mutability::Not) = *ty.kind() - && ty.is_str() - { - alloc - .provenance() - .ptrs() - .values() - .map(|&alloc| { - if let GlobalAlloc::Memory(alloc) = cx.tcx.global_alloc(alloc.alloc_id()) { - let alloc = alloc.inner(); - str::from_utf8(alloc.inspect_with_uninit_and_ptr_outside_interpreter(0..alloc.len())) - .ok() - .map(ToOwned::to_owned) - } else { - None - } - }) - .collect() - } else { - None - } -} - -fn path_from_array(exprs: &[Expr<'_>]) -> Option<Vec<String>> { - exprs - .iter() - .map(|expr| { - if let ExprKind::Lit(lit) = &expr.kind { - if let LitKind::Str(sym, _) = lit.node { - return Some((*sym.as_str()).to_owned()); - } - } - - None - }) - .collect() -} - -fn get_lang_item_name(cx: &LateContext<'_>, def_id: DefId) -> Option<&'static str> { - if let Some((lang_item, _)) = cx.tcx.lang_items().iter().find(|(_, id)| *id == def_id) { - Some(lang_item.variant_name()) - } else { - None - } -} diff --git a/clippy_lints/src/utils/internal_lints/unsorted_clippy_utils_paths.rs b/clippy_lints/src/utils/internal_lints/unsorted_clippy_utils_paths.rs deleted file mode 100644 index a5c4bf474f7..00000000000 --- a/clippy_lints/src/utils/internal_lints/unsorted_clippy_utils_paths.rs +++ /dev/null @@ -1,49 +0,0 @@ -use clippy_utils::diagnostics::span_lint; -use rustc_ast::ast::{Crate, ItemKind, ModKind}; -use rustc_lint::{EarlyContext, EarlyLintPass}; -use rustc_session::declare_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks that [`clippy_utils::paths`] is sorted lexically - /// - /// ### Why is this bad? - /// We like to pretend we're an example of tidy code. - /// - /// ### Example - /// Wrong ordering of the util::paths constants. - pub UNSORTED_CLIPPY_UTILS_PATHS, - internal, - "various things that will negatively affect your clippy experience" -} - -declare_lint_pass!(UnsortedClippyUtilsPaths => [UNSORTED_CLIPPY_UTILS_PATHS]); - -impl EarlyLintPass for UnsortedClippyUtilsPaths { - fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &Crate) { - if let Some(utils) = krate.items.iter().find(|item| item.ident.name.as_str() == "utils") { - if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = utils.kind { - if let Some(paths) = items.iter().find(|item| item.ident.name.as_str() == "paths") { - if let ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) = paths.kind { - let mut last_name: Option<&str> = None; - for item in items { - let name = item.ident.as_str(); - if let Some(last_name) = last_name { - if *last_name > *name { - span_lint( - cx, - UNSORTED_CLIPPY_UTILS_PATHS, - item.span, - "this constant should be before the previous constant due to lexical \ - ordering", - ); - } - } - last_name = Some(name); - } - } - } - } - } - } -} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 4476cd1005e..16066dd96c0 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -2,6 +2,3 @@ pub mod attr_collector; pub mod author; pub mod dump_hir; pub mod format_args_collector; - -#[cfg(feature = "internal")] -pub mod internal_lints; diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 405310512df..5b3f60ad6ab 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -68,6 +68,8 @@ declare_clippy_lint! { /// (including the standard library) provide modules named "prelude" specifically designed /// for wildcard import. /// + /// Wildcard imports reexported through `pub use` are also allowed. + /// /// `use super::*` is allowed in test modules. This is defined as any module with "test" in the name. /// /// These exceptions can be disabled using the `warn-on-all-wildcard-imports` configuration flag. @@ -121,7 +123,9 @@ impl LateLintPass<'_> for WildcardImports { } let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id); - if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) { + if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) + && !self.warn_on_all + { return; } if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind diff --git a/clippy_lints/src/zero_sized_map_values.rs b/clippy_lints/src/zero_sized_map_values.rs index f6948be7f67..a97643e0eac 100644 --- a/clippy_lints/src/zero_sized_map_values.rs +++ b/clippy_lints/src/zero_sized_map_values.rs @@ -74,10 +74,10 @@ impl LateLintPass<'_> for ZeroSizedMapValues { fn in_trait_impl(cx: &LateContext<'_>, hir_id: HirId) -> bool { let parent_id = cx.tcx.hir_get_parent_item(hir_id); let second_parent_id = cx.tcx.hir_get_parent_item(parent_id.into()).def_id; - if let Node::Item(item) = cx.tcx.hir_node_by_def_id(second_parent_id) { - if let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind { - return true; - } + if let Node::Item(item) = cx.tcx.hir_node_by_def_id(second_parent_id) + && let ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }) = item.kind + { + return true; } false } diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 7667db46968..39c1aab8967 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -131,7 +131,7 @@ struct WaitFinder<'a, 'tcx> { local_id: HirId, state: VisitorState, early_return: Option<Span>, - // When joining two if branches where one of them doesn't call `wait()`, stores its span for more targetted help + // When joining two if branches where one of them doesn't call `wait()`, stores its span for more targeted help // messages missing_wait_branch: Option<MissingWaitBranch>, } |
