diff options
| author | Philipp Krones <hello@philkrones.com> | 2025-06-13 10:17:14 +0200 |
|---|---|---|
| committer | Philipp Krones <hello@philkrones.com> | 2025-06-13 10:17:14 +0200 |
| commit | 85655d4a098be06080e4ec20360a5eb7feab3e3f (patch) | |
| tree | ac84391c2d14214eff463de30e5eaf55656b9fc7 /clippy_lints/src | |
| parent | 507183d438e615dcc24eb488a6622b5591801058 (diff) | |
| parent | 6d7c16a3d6301c6a07b2aad768e2e4113b9d5f8f (diff) | |
Merge remote-tracking branch 'upstream/master' into rustup
Diffstat (limited to 'clippy_lints/src')
64 files changed, 985 insertions, 332 deletions
diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 59a0c7c8868..b9ae9afe851 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -204,7 +204,7 @@ impl ArbitrarySourceItemOrdering { self.assoc_types_order ), Some(before_item.span), - format!("should be placed before `{}`", before_item.ident.as_str(),), + format!("should be placed before `{}`", before_item.ident.name), ); } @@ -216,7 +216,7 @@ impl ArbitrarySourceItemOrdering { ident.span, "incorrect ordering of items (must be alphabetically ordered)", Some(before_ident.span), - format!("should be placed before `{}`", before_ident.as_str(),), + format!("should be placed before `{}`", before_ident.name), ); } @@ -228,7 +228,7 @@ impl ArbitrarySourceItemOrdering { }; let (before_span, note) = if let Some(ident) = before_item.kind.ident() { - (ident.span, format!("should be placed before `{}`", ident.as_str(),)) + (ident.span, format!("should be placed before `{}`", ident.name)) } else { ( before_item.span, @@ -255,7 +255,7 @@ impl ArbitrarySourceItemOrdering { self.assoc_types_order ), Some(before_item.span), - format!("should be placed before `{}`", before_item.ident.as_str(),), + format!("should be placed before `{}`", before_item.ident.name), ); } } diff --git a/clippy_lints/src/attrs/deprecated_cfg_attr.rs b/clippy_lints/src/attrs/deprecated_cfg_attr.rs index 0edb50be8c7..d67a194b020 100644 --- a/clippy_lints/src/attrs/deprecated_cfg_attr.rs +++ b/clippy_lints/src/attrs/deprecated_cfg_attr.rs @@ -61,7 +61,7 @@ pub(super) fn check_clippy(cx: &EarlyContext<'_>, attr: &Attribute) { fn check_deprecated_cfg_recursively(cx: &EarlyContext<'_>, attr: &rustc_ast::MetaItem) { if let Some(ident) = attr.ident() { - if ["any", "all", "not"].contains(&ident.name.as_str()) { + if matches!(ident.name, sym::any | sym::all | sym::not) { let Some(list) = attr.meta_item_list() else { return }; for item in list.iter().filter_map(|item| item.meta_item()) { check_deprecated_cfg_recursively(cx, item); diff --git a/clippy_lints/src/attrs/duplicated_attributes.rs b/clippy_lints/src/attrs/duplicated_attributes.rs index a851daaede7..c2406bcfb64 100644 --- a/clippy_lints/src/attrs/duplicated_attributes.rs +++ b/clippy_lints/src/attrs/duplicated_attributes.rs @@ -1,9 +1,10 @@ use super::DUPLICATED_ATTRIBUTES; use clippy_utils::diagnostics::span_lint_and_then; +use itertools::Itertools; use rustc_ast::{Attribute, MetaItem}; use rustc_data_structures::fx::FxHashMap; use rustc_lint::EarlyContext; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; use std::collections::hash_map::Entry; fn emit_if_duplicated( @@ -29,7 +30,7 @@ fn check_duplicated_attr( cx: &EarlyContext<'_>, attr: &MetaItem, attr_paths: &mut FxHashMap<String, Span>, - parent: &mut Vec<String>, + parent: &mut Vec<Symbol>, ) { if attr.span.from_expansion() { return; @@ -43,7 +44,7 @@ fn check_duplicated_attr( return; } if let Some(direct_parent) = parent.last() - && direct_parent == sym::cfg_trace.as_str() + && *direct_parent == sym::cfg_trace && [sym::all, sym::not, sym::any].contains(&name) { // FIXME: We don't correctly check `cfg`s for now, so if it's more complex than just a one @@ -51,9 +52,14 @@ fn check_duplicated_attr( return; } if let Some(value) = attr.value_str() { - emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}={value}", parent.join(":"))); + emit_if_duplicated( + cx, + attr, + attr_paths, + format!("{}:{name}={value}", parent.iter().join(":")), + ); } else if let Some(sub_attrs) = attr.meta_item_list() { - parent.push(name.as_str().to_string()); + parent.push(name); for sub_attr in sub_attrs { if let Some(meta) = sub_attr.meta_item() { check_duplicated_attr(cx, meta, attr_paths, parent); @@ -61,7 +67,7 @@ fn check_duplicated_attr( } parent.pop(); } else { - emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.join(":"))); + emit_if_duplicated(cx, attr, attr_paths, format!("{}:{name}", parent.iter().join(":"))); } } diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 7c6fd91ca67..bf43234ff50 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -1,10 +1,10 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; -use clippy_utils::eq_expr_value; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::{eq_expr_value, sym}; use rustc_ast::ast::LitKind; use rustc_attr_data_structures::RustcVersion; use rustc_errors::Applicability; @@ -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, SyntaxContext, sym}; +use rustc_span::{Span, Symbol, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -73,10 +73,10 @@ declare_clippy_lint! { } // For each pairs, both orders are considered. -const METHODS_WITH_NEGATION: [(Option<RustcVersion>, &str, &str); 3] = [ - (None, "is_some", "is_none"), - (None, "is_err", "is_ok"), - (Some(msrvs::IS_NONE_OR), "is_some_and", "is_none_or"), +const METHODS_WITH_NEGATION: [(Option<RustcVersion>, Symbol, Symbol); 3] = [ + (None, sym::is_some, sym::is_none), + (None, sym::is_err, sym::is_ok), + (Some(msrvs::IS_NONE_OR), sym::is_some_and, sym::is_none_or), ]; pub struct NonminimalBool { @@ -440,9 +440,7 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio .iter() .copied() .flat_map(|(msrv, a, b)| vec![(msrv, a, b), (msrv, b, a)]) - .find(|&(msrv, a, _)| { - a == path.ident.name.as_str() && msrv.is_none_or(|msrv| curr_msrv.meets(cx, msrv)) - }) + .find(|&(msrv, a, _)| a == path.ident.name && msrv.is_none_or(|msrv| curr_msrv.meets(cx, msrv))) .and_then(|(_, _, neg_method)| { let negated_args = args .iter() diff --git a/clippy_lints/src/casts/cast_lossless.rs b/clippy_lints/src/casts/cast_lossless.rs index 0f066fae118..c1d6cec1b62 100644 --- a/clippy_lints/src/casts/cast_lossless.rs +++ b/clippy_lints/src/casts/cast_lossless.rs @@ -76,19 +76,20 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M return false; } - match (cast_from.is_integral(), cast_to.is_integral()) { - (true, true) => { + match ( + utils::int_ty_to_nbits(cx.tcx, cast_from), + utils::int_ty_to_nbits(cx.tcx, cast_to), + ) { + (Some(from_nbits), Some(to_nbits)) => { let cast_signed_to_unsigned = cast_from.is_signed() && !cast_to.is_signed(); - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); !is_isize_or_usize(cast_from) && !is_isize_or_usize(cast_to) && from_nbits < to_nbits && !cast_signed_to_unsigned }, - (true, false) => { - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); + (Some(from_nbits), None) => { + // FIXME: handle `f16` and `f128` let to_nbits = if let ty::Float(FloatTy::F32) = cast_to.kind() { 32 } else { @@ -96,9 +97,7 @@ fn should_lint(cx: &LateContext<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>, msrv: M }; !is_isize_or_usize(cast_from) && from_nbits < to_nbits }, - (false, true) if matches!(cast_from.kind(), ty::Bool) && msrv.meets(cx, msrvs::FROM_BOOL) => true, - (_, _) => { - matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)) - }, + (None, Some(_)) if cast_from.is_bool() && msrv.meets(cx, msrvs::FROM_BOOL) => true, + _ => matches!(cast_from.kind(), ty::Float(FloatTy::F32)) && matches!(cast_to.kind(), ty::Float(FloatTy::F64)), } } diff --git a/clippy_lints/src/casts/cast_possible_truncation.rs b/clippy_lints/src/casts/cast_possible_truncation.rs index 4120e5c8cb7..a2ecb5fb44a 100644 --- a/clippy_lints/src/casts/cast_possible_truncation.rs +++ b/clippy_lints/src/casts/cast_possible_truncation.rs @@ -91,15 +91,14 @@ pub(super) fn check( cast_to: Ty<'_>, cast_to_span: Span, ) { - let msg = match (cast_from.kind(), cast_to.is_integral()) { - (ty::Int(_) | ty::Uint(_), true) => { + let msg = match (cast_from.kind(), utils::int_ty_to_nbits(cx.tcx, cast_to)) { + (ty::Int(_) | ty::Uint(_), Some(to_nbits)) => { let from_nbits = apply_reductions( cx, - utils::int_ty_to_nbits(cast_from, cx.tcx), + utils::int_ty_to_nbits(cx.tcx, cast_from).unwrap(), cast_expr, cast_from.is_signed(), ); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); let (should_lint, suffix) = match (is_isize_or_usize(cast_from), is_isize_or_usize(cast_to)) { (true, true) | (false, false) => (to_nbits < from_nbits, ""), @@ -121,7 +120,7 @@ pub(super) fn check( format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}",) }, - (ty::Adt(def, _), true) if def.is_enum() => { + (ty::Adt(def, _), Some(to_nbits)) if def.is_enum() => { let (from_nbits, variant) = if let ExprKind::Path(p) = &cast_expr.kind && let Res::Def(DefKind::Ctor(..), id) = cx.qpath_res(p, cast_expr.hir_id) { @@ -132,7 +131,6 @@ pub(super) fn check( } else { (utils::enum_ty_to_nbits(*def, cx.tcx), None) }; - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); let cast_from_ptr_size = def.repr().int.is_none_or(|ty| matches!(ty, IntegerType::Pointer(_),)); let suffix = match (cast_from_ptr_size, is_isize_or_usize(cast_to)) { @@ -157,11 +155,11 @@ pub(super) fn check( format!("casting `{cast_from}` to `{cast_to}` may truncate the value{suffix}") }, - (ty::Float(_), true) => { + (ty::Float(_), Some(_)) => { format!("casting `{cast_from}` to `{cast_to}` may truncate the value") }, - (ty::Float(FloatTy::F64), false) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => { + (ty::Float(FloatTy::F64), None) if matches!(cast_to.kind(), &ty::Float(FloatTy::F32)) => { "casting `f64` to `f32` may truncate the value".to_string() }, diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 504d0a267e4..e26c03ccda9 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -17,9 +17,12 @@ enum EmitState { } pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !(cast_from.is_integral() && cast_to.is_integral()) { + let (Some(from_nbits), Some(to_nbits)) = ( + utils::int_ty_to_nbits(cx.tcx, cast_from), + utils::int_ty_to_nbits(cx.tcx, cast_to), + ) else { return; - } + }; // emit a lint if a cast is: // 1. unsigned to signed @@ -35,9 +38,6 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca return; } - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - let should_lint = match (cast_from.is_ptr_sized_integral(), cast_to.is_ptr_sized_integral()) { (true, true) => { // casts between two ptr sized integers are trivially always the same size diff --git a/clippy_lints/src/casts/cast_precision_loss.rs b/clippy_lints/src/casts/cast_precision_loss.rs index 1eb115ce6bd..712e38db499 100644 --- a/clippy_lints/src/casts/cast_precision_loss.rs +++ b/clippy_lints/src/casts/cast_precision_loss.rs @@ -7,15 +7,14 @@ use rustc_middle::ty::{self, FloatTy, Ty}; use super::{CAST_PRECISION_LOSS, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { - if !cast_from.is_integral() || cast_to.is_integral() { + let Some(from_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_from) else { return; - } + }; - let from_nbits = utils::int_ty_to_nbits(cast_from, cx.tcx); - let to_nbits = if cast_to.kind() == &ty::Float(FloatTy::F32) { - 32 - } else { - 64 + // FIXME: handle `f16` and `f128` + let to_nbits = match cast_to.kind() { + ty::Float(f @ (FloatTy::F32 | FloatTy::F64)) => f.bit_width(), + _ => return, }; if !(is_isize_or_usize(cast_from) || from_nbits >= to_nbits) { @@ -29,9 +28,10 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca let from_nbits_str = if arch_dependent { "64".to_owned() } else if is_isize_or_usize(cast_from) { + // FIXME: handle 16 bits `usize` type "32 or 64".to_owned() } else { - utils::int_ty_to_nbits(cast_from, cx.tcx).to_string() + from_nbits.to_string() }; span_lint( diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index 01020f3eee2..e4dafde0f9d 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -61,7 +61,7 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { }; match parent.kind { ExprKind::MethodCall(name, self_arg, ..) if self_arg.hir_id == e.hir_id => { - if matches!(name.ident.as_str(), "read_unaligned" | "write_unaligned") + if matches!(name.ident.name, sym::read_unaligned | sym::write_unaligned) && let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) && let Some(def_id) = cx.tcx.impl_of_method(def_id) && cx.tcx.type_of(def_id).instantiate_identity().is_raw_ptr() diff --git a/clippy_lints/src/casts/fn_to_numeric_cast.rs b/clippy_lints/src/casts/fn_to_numeric_cast.rs index ac1a355c8d9..105477093b5 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast.rs @@ -3,24 +3,22 @@ use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; -use rustc_middle::ty::{self, Ty, UintTy}; +use rustc_middle::ty::{self, Ty}; use super::{FN_TO_NUMERIC_CAST, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We only want to check casts to `ty::Uint` or `ty::Int` - match cast_to.kind() { - ty::Uint(_) | ty::Int(..) => { /* continue on */ }, - _ => return, - } + let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { + return; + }; match cast_from.kind() { ty::FnDef(..) | ty::FnPtr(..) => { let mut applicability = Applicability::MaybeIncorrect; - let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); - if (to_nbits >= cx.tcx.data_layout.pointer_size.bits()) && (*cast_to.kind() != ty::Uint(UintTy::Usize)) { + if to_nbits >= cx.tcx.data_layout.pointer_size.bits() && !cast_to.is_usize() { + let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); span_lint_and_sugg( cx, FN_TO_NUMERIC_CAST, diff --git a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs index 18e7798452e..700b7d0d426 100644 --- a/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs +++ b/clippy_lints/src/casts/fn_to_numeric_cast_with_truncation.rs @@ -9,16 +9,14 @@ use super::{FN_TO_NUMERIC_CAST_WITH_TRUNCATION, utils}; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, cast_from: Ty<'_>, cast_to: Ty<'_>) { // We only want to check casts to `ty::Uint` or `ty::Int` - match cast_to.kind() { - ty::Uint(_) | ty::Int(..) => { /* continue on */ }, - _ => return, - } + let Some(to_nbits) = utils::int_ty_to_nbits(cx.tcx, cast_to) else { + return; + }; match cast_from.kind() { ty::FnDef(..) | ty::FnPtr(..) => { let mut applicability = Applicability::MaybeIncorrect; let from_snippet = snippet_with_applicability(cx, cast_expr.span, "x", &mut applicability); - let to_nbits = utils::int_ty_to_nbits(cast_to, cx.tcx); if to_nbits < cx.tcx.data_layout.pointer_size.bits() { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/casts/utils.rs b/clippy_lints/src/casts/utils.rs index 5ccba92a0af..318a1646477 100644 --- a/clippy_lints/src/casts/utils.rs +++ b/clippy_lints/src/casts/utils.rs @@ -1,27 +1,14 @@ use clippy_utils::ty::{EnumValue, read_explicit_enum_value}; use rustc_middle::ty::{self, AdtDef, IntTy, Ty, TyCtxt, UintTy, VariantDiscr}; -/// Returns the size in bits of an integral type. -/// Will return 0 if the type is not an int or uint variant -pub(super) fn int_ty_to_nbits(typ: Ty<'_>, tcx: TyCtxt<'_>) -> u64 { - match typ.kind() { - ty::Int(i) => match i { - IntTy::Isize => tcx.data_layout.pointer_size.bits(), - IntTy::I8 => 8, - IntTy::I16 => 16, - IntTy::I32 => 32, - IntTy::I64 => 64, - IntTy::I128 => 128, - }, - ty::Uint(i) => match i { - UintTy::Usize => tcx.data_layout.pointer_size.bits(), - UintTy::U8 => 8, - UintTy::U16 => 16, - UintTy::U32 => 32, - UintTy::U64 => 64, - UintTy::U128 => 128, - }, - _ => 0, +/// Returns the size in bits of an integral type, or `None` if `ty` is not an +/// integral type. +pub(super) fn int_ty_to_nbits(tcx: TyCtxt<'_>, ty: Ty<'_>) -> Option<u64> { + match ty.kind() { + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(tcx.data_layout.pointer_size.bits()), + ty::Int(i) => i.bit_width(), + ty::Uint(i) => i.bit_width(), + _ => None, } } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 8ada608049c..9b3822f9d8f 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -2,11 +2,12 @@ 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::{SpanlessEq, is_in_const_context, is_integer_literal}; +use clippy_utils::{SpanlessEq, is_in_const_context, is_integer_literal, sym}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; +use rustc_span::Symbol; declare_clippy_lint! { /// ### What it does @@ -98,7 +99,7 @@ impl LateLintPass<'_> for CheckedConversions { struct Conversion<'a> { cvt: ConversionType, expr_to_cast: &'a Expr<'a>, - to_type: Option<&'a str>, + to_type: Option<Symbol>, } /// The kind of conversion that is checked @@ -150,7 +151,7 @@ impl<'a> Conversion<'a> { } /// Try to construct a new conversion if the conversion type is valid - fn try_new(expr_to_cast: &'a Expr<'_>, from_type: &str, to_type: &'a str) -> Option<Conversion<'a>> { + fn try_new(expr_to_cast: &'a Expr<'_>, from_type: Symbol, to_type: Symbol) -> Option<Conversion<'a>> { ConversionType::try_new(from_type, to_type).map(|cvt| Conversion { cvt, expr_to_cast, @@ -171,7 +172,7 @@ impl<'a> Conversion<'a> { impl ConversionType { /// Creates a conversion type if the type is allowed & conversion is valid #[must_use] - fn try_new(from: &str, to: &str) -> Option<Self> { + fn try_new(from: Symbol, to: Symbol) -> Option<Self> { if UINTS.contains(&from) { Some(Self::FromUnsigned) } else if SINTS.contains(&from) { @@ -190,7 +191,7 @@ impl ConversionType { /// Check for `expr <= (to_type::MAX as from_type)` fn check_upper_bound<'tcx>(lt: &'tcx Expr<'tcx>, gt: &'tcx Expr<'tcx>) -> Option<Conversion<'tcx>> { - if let Some((from, to)) = get_types_from_cast(gt, INTS, "max_value", "MAX") { + if let Some((from, to)) = get_types_from_cast(gt, INTS, sym::max_value, sym::MAX) { Conversion::try_new(lt, from, to) } else { None @@ -209,7 +210,7 @@ fn check_lower_bound_zero<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> O /// Check for `expr >= (to_type::MIN as from_type)` fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Option<Conversion<'a>> { - if let Some((from, to)) = get_types_from_cast(check, SINTS, "min_value", "MIN") { + if let Some((from, to)) = get_types_from_cast(check, SINTS, sym::min_value, sym::MIN) { Conversion::try_new(candidate, from, to) } else { None @@ -217,15 +218,15 @@ fn check_lower_bound_min<'a>(candidate: &'a Expr<'_>, check: &'a Expr<'_>) -> Op } /// Tries to extract the from- and to-type from a cast expression -fn get_types_from_cast<'a>( - expr: &'a Expr<'_>, - types: &'a [&str], - func: &'a str, - assoc_const: &'a str, -) -> Option<(&'a str, &'a str)> { +fn get_types_from_cast( + expr: &Expr<'_>, + types: &[Symbol], + func: Symbol, + assoc_const: Symbol, +) -> Option<(Symbol, Symbol)> { // `to_type::max_value() as from_type` // or `to_type::MAX as from_type` - let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind + let call_from_cast: Option<(&Expr<'_>, Symbol)> = if let ExprKind::Cast(limit, from_type) = &expr.kind // to_type::max_value(), from_type && let TyKind::Path(from_type_path) = &from_type.kind && let Some(from_sym) = int_ty_to_sym(from_type_path) @@ -236,12 +237,12 @@ fn get_types_from_cast<'a>( }; // `from_type::from(to_type::max_value())` - let limit_from: Option<(&Expr<'_>, &str)> = call_from_cast.or_else(|| { + let limit_from: Option<(&Expr<'_>, Symbol)> = call_from_cast.or_else(|| { if let ExprKind::Call(from_func, [limit]) = &expr.kind // `from_type::from, to_type::max_value()` // `from_type::from` && let ExprKind::Path(path) = &from_func.kind - && let Some(from_sym) = get_implementing_type(path, INTS, "from") + && let Some(from_sym) = get_implementing_type(path, INTS, sym::from) { Some((limit, from_sym)) } else { @@ -273,32 +274,41 @@ fn get_types_from_cast<'a>( } /// Gets the type which implements the called function -fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function: &str) -> Option<&'a str> { +fn get_implementing_type(path: &QPath<'_>, candidates: &[Symbol], function: Symbol) -> Option<Symbol> { if let QPath::TypeRelative(ty, path) = &path - && path.ident.name.as_str() == function + && path.ident.name == function && let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind && let [int] = tp.segments { - let name = int.ident.name.as_str(); - candidates.iter().find(|c| &name == *c).copied() + candidates.iter().find(|c| int.ident.name == **c).copied() } else { None } } /// Gets the type as a string, if it is a supported integer -fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> { +fn int_ty_to_sym(path: &QPath<'_>) -> Option<Symbol> { if let QPath::Resolved(_, path) = *path && let [ty] = path.segments { - let name = ty.ident.name.as_str(); - INTS.iter().find(|c| &name == *c).copied() + INTS.iter().find(|c| ty.ident.name == **c).copied() } else { None } } // Constants -const UINTS: &[&str] = &["u8", "u16", "u32", "u64", "usize"]; -const SINTS: &[&str] = &["i8", "i16", "i32", "i64", "isize"]; -const INTS: &[&str] = &["u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize"]; +const UINTS: &[Symbol] = &[sym::u8, sym::u16, sym::u32, sym::u64, sym::usize]; +const SINTS: &[Symbol] = &[sym::i8, sym::i16, sym::i32, sym::i64, sym::isize]; +const INTS: &[Symbol] = &[ + sym::u8, + sym::u16, + sym::u32, + sym::u64, + sym::usize, + sym::i8, + sym::i16, + sym::i32, + sym::i64, + sym::isize, +]; diff --git a/clippy_lints/src/cloned_ref_to_slice_refs.rs b/clippy_lints/src/cloned_ref_to_slice_refs.rs index 6b239a1541b..e33a8e0fb74 100644 --- a/clippy_lints/src/cloned_ref_to_slice_refs.rs +++ b/clippy_lints/src/cloned_ref_to_slice_refs.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// /// ### Why is this bad /// - /// A reference does not need to be owned in order to used as a slice. + /// A reference does not need to be owned in order to be used as a slice. /// /// ### Known problems /// diff --git a/clippy_lints/src/coerce_container_to_any.rs b/clippy_lints/src/coerce_container_to_any.rs new file mode 100644 index 00000000000..8c12a42ba4e --- /dev/null +++ b/clippy_lints/src/coerce_container_to_any.rs @@ -0,0 +1,108 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet; +use clippy_utils::sym; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, ExistentialPredicate, Ty, TyCtxt}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Protects against unintended coercion of references to container types to `&dyn Any` when the + /// container type dereferences to a `dyn Any` which could be directly referenced instead. + /// + /// ### Why is this bad? + /// + /// The intention is usually to get a reference to the `dyn Any` the value dereferences to, + /// rather than coercing a reference to the container itself to `&dyn Any`. + /// + /// ### Example + /// + /// Because `Box<dyn Any>` itself implements `Any`, `&Box<dyn Any>` + /// can be coerced to an `&dyn Any` which refers to *the `Box` itself*, rather than the + /// inner `dyn Any`. + /// ```no_run + /// # use std::any::Any; + /// let x: Box<dyn Any> = Box::new(0u32); + /// let dyn_any_of_box: &dyn Any = &x; + /// + /// // Fails as we have a &dyn Any to the Box, not the u32 + /// assert_eq!(dyn_any_of_box.downcast_ref::<u32>(), None); + /// ``` + /// Use instead: + /// ```no_run + /// # use std::any::Any; + /// let x: Box<dyn Any> = Box::new(0u32); + /// let dyn_any_of_u32: &dyn Any = &*x; + /// + /// // Succeeds since we have a &dyn Any to the inner u32! + /// assert_eq!(dyn_any_of_u32.downcast_ref::<u32>(), Some(&0u32)); + /// ``` + #[clippy::version = "1.88.0"] + pub COERCE_CONTAINER_TO_ANY, + suspicious, + "coercing to `&dyn Any` when dereferencing could produce a `dyn Any` without coercion is usually not intended" +} +declare_lint_pass!(CoerceContainerToAny => [COERCE_CONTAINER_TO_ANY]); + +impl<'tcx> LateLintPass<'tcx> for CoerceContainerToAny { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + // If this expression has an effective type of `&dyn Any` ... + { + let coerced_ty = cx.typeck_results().expr_ty_adjusted(e); + + let ty::Ref(_, coerced_ref_ty, _) = *coerced_ty.kind() else { + return; + }; + if !is_dyn_any(cx.tcx, coerced_ref_ty) { + return; + } + } + + let expr_ty = cx.typeck_results().expr_ty(e); + let ty::Ref(_, expr_ref_ty, _) = *expr_ty.kind() else { + return; + }; + // ... but only due to coercion ... + if is_dyn_any(cx.tcx, expr_ref_ty) { + return; + } + // ... and it also *derefs* to `dyn Any` ... + let Some((depth, target)) = clippy_utils::ty::deref_chain(cx, expr_ref_ty).enumerate().last() else { + return; + }; + if !is_dyn_any(cx.tcx, target) { + return; + } + + // ... that's probably not intended. + let (span, deref_count) = match e.kind { + // If `e` was already an `&` expression, skip `*&` in the suggestion + ExprKind::AddrOf(_, _, referent) => (referent.span, depth), + _ => (e.span, depth + 1), + }; + span_lint_and_sugg( + cx, + COERCE_CONTAINER_TO_ANY, + e.span, + format!("coercing `{expr_ty}` to `&dyn Any`"), + "consider dereferencing", + format!("&{}{}", str::repeat("*", deref_count), snippet(cx, span, "x")), + Applicability::MaybeIncorrect, + ); + } +} + +fn is_dyn_any(tcx: TyCtxt<'_>, ty: Ty<'_>) -> bool { + let ty::Dynamic(traits, ..) = ty.kind() else { + return false; + }; + traits.iter().any(|binder| { + let ExistentialPredicate::Trait(t) = binder.skip_binder() else { + return false; + }; + tcx.is_diagnostic_item(sym::Any, t.def_id) + }) +} diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 2467fc95fd0..5ef726638a5 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -425,7 +425,9 @@ fn scan_block_for_eq<'tcx>( modifies_any_local(cx, stmt, &cond_locals) || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) }) - .map_or(block.stmts.len(), |(i, _)| i); + .map_or(block.stmts.len(), |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, block.stmts[..i].iter().enumerate().rev()) + }); if local_needs_ordered_drop { return BlockEq { @@ -467,7 +469,9 @@ fn scan_block_for_eq<'tcx>( .is_none_or(|s| hash != hash_stmt(cx, s)) }) }) - .map_or(block.stmts.len() - start_end_eq, |(i, _)| i); + .map_or(block.stmts.len() - start_end_eq, |(i, stmt)| { + adjust_by_closest_callsite(i, stmt, (0..i).rev().zip(block.stmts[(block.stmts.len() - i)..].iter())) + }); let moved_locals_at_start = moved_locals.len(); let mut i = end_search_start; @@ -522,6 +526,49 @@ fn scan_block_for_eq<'tcx>( } } +/// Adjusts the index for which the statements begin to differ to the closest macro callsite. This +/// avoids giving suggestions that requires splitting a macro call in half, when only a part of the +/// macro expansion is equal. +/// +/// For example, for the following macro: +/// ```rust,ignore +/// macro_rules! foo { +/// ($x:expr) => { +/// let y = 42; +/// $x; +/// }; +/// } +/// ``` +/// If the macro is called like this: +/// ```rust,ignore +/// if false { +/// let z = 42; +/// foo!(println!("Hello")); +/// } else { +/// let z = 42; +/// foo!(println!("World")); +/// } +/// ``` +/// Although the expanded `let y = 42;` is equal, the macro call should not be included in the +/// suggestion. +fn adjust_by_closest_callsite<'tcx>( + i: usize, + stmt: &'tcx Stmt<'tcx>, + mut iter: impl Iterator<Item = (usize, &'tcx Stmt<'tcx>)>, +) -> usize { + let Some((_, first)) = iter.next() else { + return 0; + }; + + // If it is already at the boundary of a macro call, then just return. + if first.span.source_callsite() != stmt.span.source_callsite() { + return i; + } + + iter.find(|(_, stmt)| stmt.span.source_callsite() != first.span.source_callsite()) + .map_or(0, |(i, _)| i + 1) +} + fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { get_enclosing_block(cx, if_expr.hir_id).is_some_and(|block| { let ignore_span = block.span.shrink_to_lo().to(if_expr.span); diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index b43906903a0..695b25aeb0b 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -1,7 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -34,10 +33,12 @@ declare_lint_pass!(CreateDir => [CREATE_DIR]); impl LateLintPass<'_> for CreateDir { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Call(func, [arg]) = expr.kind + if let ExprKind::Call(func, [_]) = expr.kind && let ExprKind::Path(ref path) = func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id) + && let QPath::Resolved(_, path) = path + && let Some(last) = path.segments.last() { span_lint_and_then( cx, @@ -45,15 +46,15 @@ impl LateLintPass<'_> for CreateDir { expr.span, "calling `std::fs::create_dir` where there may be a better way", |diag| { - let mut app = Applicability::MaybeIncorrect; - diag.span_suggestion_verbose( - expr.span, + let mut suggestions = vec![(last.ident.span.shrink_to_hi(), "_all".to_owned())]; + if path.segments.len() == 1 { + suggestions.push((path.span.shrink_to_lo(), "std::fs::".to_owned())); + } + + diag.multipart_suggestion_verbose( "consider calling `std::fs::create_dir_all` instead", - format!( - "create_dir_all({})", - snippet_with_applicability(cx, arg.span, "..", &mut app) - ), - app, + suggestions, + Applicability::MaybeIncorrect, ); }, ); diff --git a/clippy_lints/src/ctfe.rs b/clippy_lints/src/ctfe.rs deleted file mode 100644 index 7bae04a10f1..00000000000 --- a/clippy_lints/src/ctfe.rs +++ /dev/null @@ -1,26 +0,0 @@ -use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, FnDecl}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::Span; - -declare_lint_pass! { - /// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). - /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran. - ClippyCtfe => [] -} - -impl<'tcx> LateLintPass<'tcx> for ClippyCtfe { - fn check_fn( - &mut self, - cx: &LateContext<'_>, - _: FnKind<'tcx>, - _: &'tcx FnDecl<'tcx>, - _: &'tcx Body<'tcx>, - _: Span, - defid: LocalDefId, - ) { - cx.tcx.ensure_ok().mir_drops_elaborated_and_const_checked(defid); // Lint - } -} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 5fcb851dfeb..1e3907d9ede 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -77,6 +77,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::cfg_not_test::CFG_NOT_TEST_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::cloned_ref_to_slice_refs::CLONED_REF_TO_SLICE_REFS_INFO, + crate::coerce_container_to_any::COERCE_CONTAINER_TO_ANY_INFO, crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, crate::collapsible_if::COLLAPSIBLE_IF_INFO, @@ -119,6 +120,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::doc::DOC_MARKDOWN_INFO, crate::doc::DOC_NESTED_REFDEFS_INFO, crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, + crate::doc::DOC_SUSPICIOUS_FOOTNOTES_INFO, crate::doc::EMPTY_DOCS_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, crate::doc::MISSING_PANICS_DOC_INFO, @@ -166,6 +168,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO, crate::format::USELESS_FORMAT_INFO, crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO, + crate::format_args::POINTER_FORMAT_INFO, crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO, crate::format_args::UNINLINED_FORMAT_ARGS_INFO, crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO, @@ -211,6 +214,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::indexing_slicing::INDEXING_SLICING_INFO, crate::indexing_slicing::OUT_OF_BOUNDS_INDEXING_INFO, crate::ineffective_open_options::INEFFECTIVE_OPEN_OPTIONS_INFO, + crate::infallible_try_from::INFALLIBLE_TRY_FROM_INFO, crate::infinite_iter::INFINITE_ITER_INFO, crate::infinite_iter::MAYBE_INFINITE_ITER_INFO, crate::inherent_impl::MULTIPLE_INHERENT_IMPL_INFO, @@ -379,6 +383,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::INSPECT_FOR_EACH_INFO, crate::methods::INTO_ITER_ON_REF_INFO, crate::methods::IO_OTHER_ERROR_INFO, + crate::methods::IP_CONSTANT_INFO, crate::methods::IS_DIGIT_ASCII_RADIX_INFO, crate::methods::ITERATOR_STEP_BY_ZERO_INFO, crate::methods::ITER_CLONED_COLLECT_INFO, diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index 9814d4fa84f..25b7099c855 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -1,4 +1,3 @@ - use clippy_config::Conf; use clippy_config::types::{DisallowedPath, create_disallowed_map}; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index f55b0cf1c50..566aa12b08f 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -1,6 +1,6 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; +use clippy_utils::{is_from_proc_macro, is_in_test}; use rustc_data_structures::fx::FxHashSet; use rustc_hir::{Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -43,8 +43,10 @@ impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); impl<'tcx> LateLintPass<'tcx> for DisallowedNames { fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { if let PatKind::Binding(.., ident, _) = pat.kind + && !ident.span.from_expansion() && self.disallow.contains(&ident.name) && !is_in_test(cx.tcx, pat.hir_id) + && !is_from_proc_macro(cx, &ident) { span_lint( cx, diff --git a/clippy_lints/src/disallowed_types.rs b/clippy_lints/src/disallowed_types.rs index 821bb25d2ce..7875cdd77e8 100644 --- a/clippy_lints/src/disallowed_types.rs +++ b/clippy_lints/src/disallowed_types.rs @@ -105,10 +105,10 @@ impl_lint_pass!(DisallowedTypes => [DISALLOWED_TYPES]); impl<'tcx> LateLintPass<'tcx> for DisallowedTypes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind { - if let Some(res) = path.res.type_ns { - self.check_res_emit(cx, &res, item.span); - } + if let ItemKind::Use(path, UseKind::Single(_)) = &item.kind + && let Some(res) = path.res.type_ns + { + self.check_res_emit(cx, &res, item.span); } } diff --git a/clippy_lints/src/doc/doc_suspicious_footnotes.rs b/clippy_lints/src/doc/doc_suspicious_footnotes.rs new file mode 100644 index 00000000000..289b6b915d4 --- /dev/null +++ b/clippy_lints/src/doc/doc_suspicious_footnotes.rs @@ -0,0 +1,113 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::token::CommentKind; +use rustc_errors::Applicability; +use rustc_hir::{AttrStyle, Attribute}; +use rustc_lint::{LateContext, LintContext}; +use rustc_resolve::rustdoc::DocFragmentKind; + +use std::ops::Range; + +use super::{DOC_SUSPICIOUS_FOOTNOTES, Fragments}; + +pub fn check(cx: &LateContext<'_>, doc: &str, range: Range<usize>, fragments: &Fragments<'_>, attrs: &[Attribute]) { + for i in doc[range.clone()] + .bytes() + .enumerate() + .filter_map(|(i, c)| if c == b'[' { Some(i) } else { None }) + { + let start = i + range.start; + if doc.as_bytes().get(start + 1) == Some(&b'^') + && let Some(end) = all_numbers_upto_brace(doc, start + 2) + && doc.as_bytes().get(end) != Some(&b':') + && doc.as_bytes().get(start - 1) != Some(&b'\\') + && let Some(this_fragment) = { + // the `doc` string contains all fragments concatenated together + // figure out which one this suspicious footnote comes from + let mut starting_position = 0; + let mut found_fragment = fragments.fragments.last(); + for fragment in fragments.fragments { + if start >= starting_position && start < starting_position + fragment.doc.as_str().len() { + found_fragment = Some(fragment); + break; + } + starting_position += fragment.doc.as_str().len(); + } + found_fragment + } + { + let span = fragments.span(cx, start..end).unwrap_or(this_fragment.span); + span_lint_and_then( + cx, + DOC_SUSPICIOUS_FOOTNOTES, + span, + "looks like a footnote ref, but has no matching footnote", + |diag| { + if this_fragment.kind == DocFragmentKind::SugaredDoc { + let (doc_attr, (_, doc_attr_comment_kind)) = attrs + .iter() + .filter(|attr| attr.span().overlaps(this_fragment.span)) + .rev() + .find_map(|attr| Some((attr, attr.doc_str_and_comment_kind()?))) + .unwrap(); + let (to_add, terminator) = match (doc_attr_comment_kind, doc_attr.style()) { + (CommentKind::Line, AttrStyle::Outer) => ("\n///\n/// ", ""), + (CommentKind::Line, AttrStyle::Inner) => ("\n//!\n//! ", ""), + (CommentKind::Block, AttrStyle::Outer) => ("\n/** ", " */"), + (CommentKind::Block, AttrStyle::Inner) => ("\n/*! ", " */"), + }; + diag.span_suggestion_verbose( + doc_attr.span().shrink_to_hi(), + "add footnote definition", + format!( + "{to_add}{label}: <!-- description -->{terminator}", + label = &doc[start..end] + ), + Applicability::HasPlaceholders, + ); + } else { + let is_file_include = cx + .sess() + .source_map() + .span_to_snippet(this_fragment.span) + .as_ref() + .map(|vdoc| vdoc.trim()) + == Ok(doc); + if is_file_include { + // if this is a file include, then there's no quote marks + diag.span_suggestion_verbose( + this_fragment.span.shrink_to_hi(), + "add footnote definition", + format!("\n\n{label}: <!-- description -->", label = &doc[start..end],), + Applicability::HasPlaceholders, + ); + } else { + // otherwise, we wrap in a string + diag.span_suggestion_verbose( + this_fragment.span, + "add footnote definition", + format!( + "r#\"{doc}\n\n{label}: <!-- description -->\"#", + doc = this_fragment.doc, + label = &doc[start..end], + ), + Applicability::HasPlaceholders, + ); + } + } + }, + ); + } + } +} + +fn all_numbers_upto_brace(text: &str, i: usize) -> Option<usize> { + for (j, c) in text.as_bytes()[i..].iter().copied().enumerate().take(64) { + if c == b']' && j != 0 { + return Some(i + j + 1); + } + if !c.is_ascii_digit() || j >= 64 { + break; + } + } + None +} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index c46dd09d60c..e0fc2fd9347 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -25,6 +25,7 @@ use std::ops::Range; use url::Url; mod doc_comment_double_space_linebreaks; +mod doc_suspicious_footnotes; mod include_in_doc_without_cfg; mod lazy_continuation; mod link_with_quotes; @@ -607,6 +608,37 @@ declare_clippy_lint! { "double-space used for doc comment linebreak instead of `\\`" } +declare_clippy_lint! { + /// ### What it does + /// Detects syntax that looks like a footnote reference. + /// + /// Rustdoc footnotes are compatible with GitHub-Flavored Markdown (GFM). + /// GFM does not parse a footnote reference unless its definition also + /// exists. This lint checks for footnote references with missing + /// definitions, unless it thinks you're writing a regex. + /// + /// ### Why is this bad? + /// This probably means that a footnote was meant to exist, + /// but was not written. + /// + /// ### Example + /// ```no_run + /// /// This is not a footnote[^1], because no definition exists. + /// fn my_fn() {} + /// ``` + /// Use instead: + /// ```no_run + /// /// This is a footnote[^1]. + /// /// + /// /// [^1]: defined here + /// fn my_fn() {} + /// ``` + #[clippy::version = "1.88.0"] + pub DOC_SUSPICIOUS_FOOTNOTES, + suspicious, + "looks like a link or footnote ref, but with no definition" +} + pub struct Documentation { valid_idents: FxHashSet<String>, check_private_items: bool, @@ -638,7 +670,8 @@ impl_lint_pass!(Documentation => [ DOC_OVERINDENTED_LIST_ITEMS, TOO_LONG_FIRST_DOC_PARAGRAPH, DOC_INCLUDE_WITHOUT_CFG, - DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS + DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS, + DOC_SUSPICIOUS_FOOTNOTES, ]); impl EarlyLintPass for Documentation { @@ -825,6 +858,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[ doc: &doc, fragments: &fragments, }, + attrs, )) } @@ -905,6 +939,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize events: Events, doc: &str, fragments: Fragments<'_>, + attrs: &[Attribute], ) -> DocHeaders { // true if a safety header was found let mut headers = DocHeaders::default(); @@ -1148,7 +1183,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize // Don't check the text associated with external URLs continue; } - text_to_check.push((text, range, code_level)); + text_to_check.push((text, range.clone(), code_level)); + doc_suspicious_footnotes::check(cx, doc, range, &fragments, attrs); } } FootnoteReference(_) => {} diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index a7670ffce88..75db923b1c3 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -1,6 +1,6 @@ use crate::Lint; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_lint_allowed; +use clippy_utils::{is_lint_allowed, sym}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Ty; @@ -65,9 +65,9 @@ declare_clippy_lint! { declare_lint_pass!(EndianBytes => [HOST_ENDIAN_BYTES, LITTLE_ENDIAN_BYTES, BIG_ENDIAN_BYTES]); -const HOST_NAMES: [&str; 2] = ["from_ne_bytes", "to_ne_bytes"]; -const LITTLE_NAMES: [&str; 2] = ["from_le_bytes", "to_le_bytes"]; -const BIG_NAMES: [&str; 2] = ["from_be_bytes", "to_be_bytes"]; +const HOST_NAMES: [Symbol; 2] = [sym::from_ne_bytes, sym::to_ne_bytes]; +const LITTLE_NAMES: [Symbol; 2] = [sym::from_le_bytes, sym::to_le_bytes]; +const BIG_NAMES: [Symbol; 2] = [sym::from_be_bytes, sym::to_be_bytes]; #[derive(Clone, Debug)] enum LintKind { @@ -95,7 +95,7 @@ impl LintKind { } } - fn as_name(&self, prefix: Prefix) -> &str { + fn as_name(&self, prefix: Prefix) -> Symbol { let index = usize::from(prefix == Prefix::To); match self { @@ -133,7 +133,7 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix let le = LintKind::Little.as_name(prefix); let be = LintKind::Big.as_name(prefix); - let (lint, other_lints) = match name.as_str() { + let (lint, other_lints) = match name { name if name == ne => ((&LintKind::Host), [(&LintKind::Little), (&LintKind::Big)]), name if name == le => ((&LintKind::Little), [(&LintKind::Host), (&LintKind::Big)]), name if name == be => ((&LintKind::Big), [(&LintKind::Host), (&LintKind::Little)]), diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index a26e736c7ae..0c39aae9ca9 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -1,6 +1,8 @@ +use std::collections::hash_map::Entry; + use arrayvec::ArrayVec; use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::macros::{ FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call, @@ -9,7 +11,7 @@ use clippy_utils::macros::{ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::{implements_trait, is_type_lang_item}; -use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test}; +use clippy_utils::{is_diag_trait_item, is_from_proc_macro, is_in_test, trait_ref_of_method}; use itertools::Itertools; use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions, @@ -22,10 +24,12 @@ use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode}; use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; -use rustc_middle::ty::{List, Ty, TyCtxt}; +use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast}; use rustc_session::impl_lint_pass; use rustc_span::edition::Edition::Edition2021; use rustc_span::{Span, Symbol, sym}; +use rustc_trait_selection::infer::TyCtxtInferExt; +use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext}; declare_clippy_lint! { /// ### What it does @@ -194,12 +198,41 @@ declare_clippy_lint! { "use of a format specifier that has no effect" } +declare_clippy_lint! { + /// ### What it does + /// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers + /// or any types that have a derived `Debug` impl that recursively contains them. + /// + /// ### Why restrict this? + /// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of + /// certain data structures or functions from prying hacker eyes as an additional line of security. + /// + /// ### Known problems + /// The lint currently only looks through derived `Debug` implementations. Checking whether a manual + /// implementation prints an address is left as an exercise to the next lint implementer. + /// + /// ### Example + /// ```no_run + /// let foo = &0_u32; + /// fn bar() {} + /// println!("{:p}", foo); + /// let _ = format!("{:?}", &(bar as fn())); + /// ``` + /// + /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits + #[clippy::version = "1.88.0"] + pub POINTER_FORMAT, + restriction, + "formatting a pointer" +} + impl_lint_pass!(FormatArgs<'_> => [ FORMAT_IN_FORMAT_ARGS, TO_STRING_IN_FORMAT_ARGS, UNINLINED_FORMAT_ARGS, UNNECESSARY_DEBUG_FORMATTING, UNUSED_FORMAT_SPECS, + POINTER_FORMAT, ]); #[allow(clippy::struct_field_names)] @@ -208,6 +241,8 @@ pub struct FormatArgs<'tcx> { msrv: Msrv, ignore_mixed: bool, ty_msrv_map: FxHashMap<Ty<'tcx>, Option<RustcVersion>>, + has_derived_debug: FxHashMap<Ty<'tcx>, bool>, + has_pointer_format: FxHashMap<Ty<'tcx>, bool>, } impl<'tcx> FormatArgs<'tcx> { @@ -218,6 +253,8 @@ impl<'tcx> FormatArgs<'tcx> { msrv: conf.msrv, ignore_mixed: conf.allow_mixed_uninlined_format_args, ty_msrv_map, + has_derived_debug: FxHashMap::default(), + has_pointer_format: FxHashMap::default(), } } } @@ -228,7 +265,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> { && is_format_macro(cx, macro_call.def_id) && let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { - let linter = FormatArgsExpr { + let mut linter = FormatArgsExpr { cx, expr, macro_call: ¯o_call, @@ -236,6 +273,8 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> { ignore_mixed: self.ignore_mixed, msrv: &self.msrv, ty_msrv_map: &self.ty_msrv_map, + has_derived_debug: &mut self.has_derived_debug, + has_pointer_format: &mut self.has_pointer_format, }; linter.check_templates(); @@ -255,10 +294,12 @@ struct FormatArgsExpr<'a, 'tcx> { ignore_mixed: bool, msrv: &'a Msrv, ty_msrv_map: &'a FxHashMap<Ty<'tcx>, Option<RustcVersion>>, + has_derived_debug: &'a mut FxHashMap<Ty<'tcx>, bool>, + has_pointer_format: &'a mut FxHashMap<Ty<'tcx>, bool>, } impl<'tcx> FormatArgsExpr<'_, 'tcx> { - fn check_templates(&self) { + fn check_templates(&mut self) { for piece in &self.format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece && let Ok(index) = placeholder.argument.index @@ -279,6 +320,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { if placeholder.format_trait == FormatTrait::Debug { let name = self.cx.tcx.item_name(self.macro_call.def_id); self.check_unnecessary_debug_formatting(name, arg_expr); + if let Some(span) = placeholder.span + && self.has_pointer_debug(self.cx.typeck_results().expr_ty(arg_expr), 0) + { + span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); + } + } + + if placeholder.format_trait == FormatTrait::Pointer + && let Some(span) = placeholder.span + { + span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected"); } } } @@ -490,6 +542,16 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { && let ty = cx.typeck_results().expr_ty(value) && self.can_display_format(ty) { + // If the parent function is a method of `Debug`, we don't want to lint + // because it is likely that the user wants to use `Debug` formatting. + let parent_fn = cx.tcx.hir_get_parent_item(value.hir_id); + if let Some(trait_ref) = trait_ref_of_method(cx, parent_fn) + && let Some(trait_def_id) = trait_ref.trait_def_id() + && cx.tcx.is_diagnostic_item(sym::Debug, trait_def_id) + { + return; + } + let snippet = snippet(cx.sess(), value.span, ".."); span_lint_and_then( cx, @@ -559,6 +621,58 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> { false } + + fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { + let cx = self.cx; + let tcx = cx.tcx; + if !tcx.recursion_limit().value_within_limit(depth) { + return false; + } + let depth = depth + 1; + let typing_env = cx.typing_env(); + let ty = tcx.normalize_erasing_regions(typing_env, ty); + match ty.kind() { + ty::RawPtr(..) | ty::FnPtr(..) | ty::FnDef(..) => true, + ty::Ref(_, t, _) | ty::Slice(t) | ty::Array(t, _) => self.has_pointer_debug(*t, depth), + ty::Tuple(ts) => ts.iter().any(|t| self.has_pointer_debug(t, depth)), + ty::Adt(adt, args) => { + match self.has_pointer_format.entry(ty) { + Entry::Occupied(o) => return *o.get(), + Entry::Vacant(v) => v.insert(false), + }; + let derived_debug = if let Some(&known) = self.has_derived_debug.get(&ty) { + known + } else { + let Some(trait_id) = tcx.get_diagnostic_item(sym::Debug) else { + return false; + }; + let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env); + let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)]); + let obligation = Obligation { + cause: ObligationCause::dummy(), + param_env, + recursion_depth: 0, + predicate: trait_ref.upcast(tcx), + }; + let selection = SelectionContext::new(&infcx).select(&obligation); + let derived = if let Ok(Some(Selection::UserDefined(data))) = selection { + tcx.has_attr(data.impl_def_id, sym::automatically_derived) + } else { + false + }; + self.has_derived_debug.insert(ty, derived); + derived + }; + let pointer_debug = derived_debug + && adt.all_fields().any(|f| { + self.has_pointer_debug(tcx.normalize_erasing_regions(typing_env, f.ty(tcx, args)), depth) + }); + self.has_pointer_format.insert(ty, pointer_debug); + pointer_debug + }, + _ => false, + } + } } fn make_ty_msrv_map(tcx: TyCtxt<'_>) -> FxHashMap<Ty<'_>, Option<RustcVersion>> { diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index d0d02a382d1..6051dc9479b 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -303,7 +303,7 @@ declare_clippy_lint! { /// to the name of the method, when there is a field's whose name matches that of the method. /// /// ### Why is this bad? - /// It is most likely that such a method is a bug caused by a typo or by copy-pasting. + /// It is most likely that such a method is a bug caused by a typo or by copy-pasting. /// /// ### Example diff --git a/clippy_lints/src/if_let_mutex.rs b/clippy_lints/src/if_let_mutex.rs index 6444e99ae9c..a99118f90f8 100644 --- a/clippy_lints/src/if_let_mutex.rs +++ b/clippy_lints/src/if_let_mutex.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{eq_expr_value, higher}; +use clippy_utils::{eq_expr_value, higher, sym}; use core::ops::ControlFlow; use rustc_errors::Diag; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::edition::Edition::Edition2024; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -94,7 +93,7 @@ fn mutex_lock_call<'tcx>( op_mutex: Option<&'tcx Expr<'_>>, ) -> ControlFlow<&'tcx Expr<'tcx>> { if let ExprKind::MethodCall(path, self_arg, [], _) = &expr.kind - && path.ident.as_str() == "lock" + && path.ident.name == sym::lock && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() && is_type_diagnostic_item(cx, ty, sym::Mutex) && op_mutex.is_none_or(|op| eq_expr_value(cx, self_arg, op)) diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 0823ef53ef9..c743501da25 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::{ - SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, + SpanlessEq, eq_expr_value, higher, is_in_const_context, is_integer_literal, peel_blocks, peel_blocks_with_stmt, sym, }; use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; @@ -11,7 +11,7 @@ use rustc_errors::Applicability; use rustc_hir::{AssignOpKind, BinOp, BinOpKind, Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -325,7 +325,7 @@ fn check_with_condition<'tcx>( } // Get the variable name - let var_name = ares_path.segments[0].ident.name.as_str(); + let var_name = ares_path.segments[0].ident.name; match cond_num_val.kind { ExprKind::Lit(cond_lit) => { // Check if the constant is zero @@ -337,7 +337,7 @@ fn check_with_condition<'tcx>( } }, ExprKind::Path(QPath::TypeRelative(_, name)) => { - if name.ident.as_str() == "MIN" + if name.ident.name == sym::MIN && let Some(const_id) = cx.typeck_results().type_dependent_def_id(cond_num_val.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(const_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl @@ -348,7 +348,7 @@ fn check_with_condition<'tcx>( }, ExprKind::Call(func, []) => { if let ExprKind::Path(QPath::TypeRelative(_, name)) = func.kind - && name.ident.as_str() == "min_value" + && name.ident.name == sym::min_value && let Some(func_id) = cx.typeck_results().type_dependent_def_id(func.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(func_id) && let None = cx.tcx.impl_trait_ref(impl_id) // An inherent impl @@ -383,7 +383,7 @@ fn subtracts_one<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<&'a Exp } } -fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: &str, expr: &Expr<'_>) { +fn print_lint_and_sugg(cx: &LateContext<'_>, var_name: Symbol, expr: &Expr<'_>) { span_lint_and_sugg( cx, IMPLICIT_SATURATING_SUB, diff --git a/clippy_lints/src/infallible_try_from.rs b/clippy_lints/src/infallible_try_from.rs new file mode 100644 index 00000000000..b54c289fa7e --- /dev/null +++ b/clippy_lints/src/infallible_try_from.rs @@ -0,0 +1,76 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::sym; +use rustc_errors::MultiSpan; +use rustc_hir::{AssocItemKind, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds manual impls of `TryFrom` with infallible error types. + /// + /// ### Why is this bad? + /// + /// Infalliable conversions should be implemented via `From` with the blanket conversion. + /// + /// ### Example + /// ```no_run + /// use std::convert::Infallible; + /// struct MyStruct(i16); + /// impl TryFrom<i16> for MyStruct { + /// type Error = Infallible; + /// fn try_from(other: i16) -> Result<Self, Infallible> { + /// Ok(Self(other.into())) + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// struct MyStruct(i16); + /// impl From<i16> for MyStruct { + /// fn from(other: i16) -> Self { + /// Self(other) + /// } + /// } + /// ``` + #[clippy::version = "1.88.0"] + pub INFALLIBLE_TRY_FROM, + suspicious, + "TryFrom with infallible Error type" +} +declare_lint_pass!(InfallibleTryFrom => [INFALLIBLE_TRY_FROM]); + +impl<'tcx> LateLintPass<'tcx> for InfallibleTryFrom { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + let ItemKind::Impl(imp) = item.kind else { return }; + let Some(r#trait) = imp.of_trait else { return }; + let Some(trait_def_id) = r#trait.trait_def_id() else { + return; + }; + if !cx.tcx.is_diagnostic_item(sym::TryFrom, trait_def_id) { + return; + } + for ii in imp.items { + if ii.kind == AssocItemKind::Type { + let ii = cx.tcx.hir_impl_item(ii.id); + if ii.ident.name != sym::Error { + continue; + } + let ii_ty = ii.expect_type(); + let ii_ty_span = ii_ty.span; + let ii_ty = clippy_utils::ty::ty_from_hir_ty(cx, ii_ty); + if !ii_ty.is_inhabited_from(cx.tcx, ii.owner_id.to_def_id(), cx.typing_env()) { + let mut span = MultiSpan::from_span(cx.tcx.def_span(item.owner_id.to_def_id())); + span.push_span_label(ii_ty_span, "infallible error type"); + span_lint( + cx, + INFALLIBLE_TRY_FROM, + span, + "infallible TryFrom impl; consider implementing From, instead", + ); + } + } + } + } +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 92eb3d7a7c5..be9142b17fe 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -55,6 +55,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; extern crate rustc_trait_selection; +extern crate smallvec; extern crate thin_vec; #[macro_use] @@ -65,11 +66,10 @@ extern crate clippy_utils; mod utils; -pub mod ctfe; // Very important lint, do not remove (rust#125116) pub mod declared_lints; pub mod deprecated_lints; -// begin lints modules, do not remove this comment, it’s used in `update_lints` +// begin lints modules, do not remove this comment, it's used in `update_lints` mod absolute_paths; mod almost_complete_range; mod approx_const; @@ -95,6 +95,7 @@ mod casts; mod cfg_not_test; mod checked_conversions; mod cloned_ref_to_slice_refs; +mod coerce_container_to_any; mod cognitive_complexity; mod collapsible_if; mod collection_is_never_read; @@ -169,6 +170,7 @@ mod inconsistent_struct_constructor; mod index_refutable_slice; mod indexing_slicing; mod ineffective_open_options; +mod infallible_try_from; mod infinite_iter; mod inherent_impl; mod inherent_to_string; @@ -404,7 +406,7 @@ mod zero_div_zero; mod zero_repeat_side_effects; mod zero_sized_map_values; mod zombie_processes; -// end lints modules, do not remove this comment, it’s used in `update_lints` +// end lints modules, do not remove this comment, it's used in `update_lints` use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; @@ -583,8 +585,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()))); - store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe)); - store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))); store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|_| Box::new(utils::author::Author)); @@ -946,5 +946,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap)); store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)); store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); + store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); + store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 02afe9f0997..42fe386d2c3 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -8,7 +8,7 @@ use clippy_utils::ty::implements_trait; use clippy_utils::visitors::is_const_evaluatable; use clippy_utils::{ MaybePath, eq_expr_value, is_diag_trait_item, is_in_const_context, is_trait_method, path_res, path_to_local_id, - peel_blocks, peel_blocks_with_stmt, + peel_blocks, peel_blocks_with_stmt, sym, }; use itertools::Itertools; use rustc_errors::{Applicability, Diag}; @@ -18,7 +18,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::Ty; use rustc_session::impl_lint_pass; use rustc_span::Span; -use rustc_span::symbol::sym; use std::cmp::Ordering; use std::ops::Deref; @@ -299,9 +298,9 @@ fn is_max_min_pattern<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> O && (cx.typeck_results().expr_ty_adjusted(input).is_floating_point() || is_trait_method(cx, receiver, sym::Ord)) { let is_float = cx.typeck_results().expr_ty_adjusted(input).is_floating_point(); - let (min, max) = match (seg_first.ident.as_str(), seg_second.ident.as_str()) { - ("min", "max") => (arg_second, arg_first), - ("max", "min") => (arg_first, arg_second), + let (min, max) = match (seg_first.ident.name, seg_second.ident.name) { + (sym::min, sym::max) => (arg_second, arg_first), + (sym::max, sym::min) => (arg_first, arg_second), _ => return None, }; Some(ClampSuggestion { diff --git a/clippy_lints/src/manual_string_new.rs b/clippy_lints/src/manual_string_new.rs index 73ee1c3c78a..9b590e092d0 100644 --- a/clippy_lints/src/manual_string_new.rs +++ b/clippy_lints/src/manual_string_new.rs @@ -1,11 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sym; use rustc_ast::LitKind; use rustc_errors::Applicability::MachineApplicable; use rustc_hir::{Expr, ExprKind, PathSegment, QPath, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -89,9 +90,10 @@ fn warn_then_suggest(cx: &LateContext<'_>, span: Span) { /// Tries to parse an expression as a method call, emitting the warning if necessary. fn parse_method_call(cx: &LateContext<'_>, span: Span, path_segment: &PathSegment<'_>, receiver: &Expr<'_>) { - let ident = path_segment.ident.as_str(); let method_arg_kind = &receiver.kind; - if ["to_string", "to_owned", "into"].contains(&ident) && is_expr_kind_empty_str(method_arg_kind) { + if matches!(path_segment.ident.name, sym::to_string | sym::to_owned | sym::into) + && is_expr_kind_empty_str(method_arg_kind) + { warn_then_suggest(cx, span); } else if let ExprKind::Call(func, [arg]) = method_arg_kind { // If our first argument is a function call itself, it could be an `unwrap`-like function. diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index 2a5fc8b6609..e0cb5d14d3c 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,12 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{higher, is_res_lang_ctor}; +use clippy_utils::{higher, is_res_lang_ctor, sym}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -57,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result<T,E>.ok(, _) && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation - && ok_path.ident.as_str() == "ok" + && ok_path.ident.name == sym::ok && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) && is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome) && let ctxt = expr.span.ctxt() diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index adda3586990..6a76c6cedd0 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -1,7 +1,7 @@ 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_context}; -use clippy_utils::{get_parent_expr, is_refutable, peel_blocks}; +use clippy_utils::{is_refutable, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind}; use rustc_lint::LateContext; @@ -9,6 +9,7 @@ use rustc_span::Span; use super::MATCH_SINGLE_BINDING; +#[derive(Debug)] enum AssignmentExpr { Assign { span: Span, match_span: Span }, Local { span: Span, pat_span: Span }, @@ -160,6 +161,17 @@ fn opt_parent_assign_span<'a>(cx: &LateContext<'a>, ex: &Expr<'a>) -> Option<Ass None } +fn expr_parent_requires_curlies<'a>(cx: &LateContext<'a>, match_expr: &Expr<'a>) -> bool { + let parent = cx.tcx.parent_hir_node(match_expr.hir_id); + matches!( + parent, + Node::Expr(Expr { + kind: ExprKind::Closure { .. }, + .. + }) | Node::AnonConst(..) + ) +} + fn sugg_with_curlies<'a>( cx: &LateContext<'a>, (ex, match_expr): (&Expr<'a>, &Expr<'a>), @@ -172,9 +184,7 @@ 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) - && let ExprKind::Closure { .. } = parent_expr.kind - { + if expr_parent_requires_curlies(cx, match_expr) { cbrace_end = format!("\n{indent}}}"); // Fix body indent due to the closure indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 65b93a095b9..8b4c1700051 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,6 +1,7 @@ use std::ops::ControlFlow; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sym; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -42,7 +43,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { type Result = ControlFlow<CaseMethod>; fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind { - let result = self.case_altered(segment.ident.as_str(), receiver); + let result = self.case_altered(segment.ident.name, receiver); if result.is_break() { return result; } @@ -53,7 +54,7 @@ impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> { + fn case_altered(&mut self, segment_ident: Symbol, receiver: &Expr<'_>) -> ControlFlow<CaseMethod> { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); @@ -66,12 +67,12 @@ impl MatchExprVisitor<'_, '_> { } } -fn get_case_method(segment_ident_str: &str) -> Option<CaseMethod> { - match segment_ident_str { - "to_lowercase" => Some(CaseMethod::LowerCase), - "to_ascii_lowercase" => Some(CaseMethod::AsciiLowerCase), - "to_uppercase" => Some(CaseMethod::UpperCase), - "to_ascii_uppercase" => Some(CaseMethod::AsciiUppercase), +fn get_case_method(segment_ident: Symbol) -> Option<CaseMethod> { + match segment_ident { + sym::to_lowercase => Some(CaseMethod::LowerCase), + sym::to_ascii_lowercase => Some(CaseMethod::AsciiLowerCase), + sym::to_uppercase => Some(CaseMethod::UpperCase), + sym::to_ascii_uppercase => Some(CaseMethod::AsciiUppercase), _ => None, } } diff --git a/clippy_lints/src/methods/ip_constant.rs b/clippy_lints/src/methods/ip_constant.rs new file mode 100644 index 00000000000..83803fba6a1 --- /dev/null +++ b/clippy_lints/src/methods/ip_constant.rs @@ -0,0 +1,52 @@ +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, QPath, Ty, TyKind}; +use rustc_lint::LateContext; +use rustc_span::sym; +use smallvec::SmallVec; + +use super::IP_CONSTANT; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, func: &Expr<'_>, args: &[Expr<'_>]) { + if let ExprKind::Path(QPath::TypeRelative( + Ty { + kind: TyKind::Path(QPath::Resolved(_, func_path)), + .. + }, + p, + )) = func.kind + && p.ident.name == sym::new + && let Some(func_def_id) = func_path.res.opt_def_id() + && matches!( + cx.tcx.get_diagnostic_name(func_def_id), + Some(sym::Ipv4Addr | sym::Ipv6Addr) + ) + && let Some(args) = args + .iter() + .map(|arg| { + if let Some(Constant::Int(constant @ (0 | 1 | 127 | 255))) = ConstEvalCtxt::new(cx).eval(arg) { + u8::try_from(constant).ok() + } else { + None + } + }) + .collect::<Option<SmallVec<[u8; 8]>>>() + { + let constant_name = match args.as_slice() { + [0, 0, 0, 0] | [0, 0, 0, 0, 0, 0, 0, 0] => "UNSPECIFIED", + [127, 0, 0, 1] | [0, 0, 0, 0, 0, 0, 0, 1] => "LOCALHOST", + [255, 255, 255, 255] => "BROADCAST", + _ => return, + }; + + span_lint_and_then(cx, IP_CONSTANT, expr.span, "hand-coded well-known IP address", |diag| { + diag.span_suggestion_verbose( + expr.span.with_lo(p.ident.span.lo()), + "use", + constant_name, + Applicability::MachineApplicable, + ); + }); + } +} diff --git a/clippy_lints/src/methods/iter_kv_map.rs b/clippy_lints/src/methods/iter_kv_map.rs index c88462129af..cbb1b450e60 100644 --- a/clippy_lints/src/methods/iter_kv_map.rs +++ b/clippy_lints/src/methods/iter_kv_map.rs @@ -46,7 +46,7 @@ pub(super) fn check<'tcx>( if let ExprKind::Path(rustc_hir::QPath::Resolved(_, path)) = body_expr.kind && let [local_ident] = path.segments - && local_ident.ident.as_str() == bound_ident.as_str() + && local_ident.ident.name == bound_ident.name { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/manual_saturating_arithmetic.rs b/clippy_lints/src/methods/manual_saturating_arithmetic.rs index e2df8ce1513..c785b23bc9c 100644 --- a/clippy_lints/src/methods/manual_saturating_arithmetic.rs +++ b/clippy_lints/src/methods/manual_saturating_arithmetic.rs @@ -72,9 +72,9 @@ fn is_min_or_max(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<MinMax> { if let hir::ExprKind::Call(func, []) = &expr.kind && let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind { - match segment.ident.as_str() { - "max_value" => return Some(MinMax::Max), - "min_value" => return Some(MinMax::Min), + match segment.ident.name { + sym::max_value => return Some(MinMax::Max), + sym::min_value => return Some(MinMax::Min), _ => {}, } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index bc159206985..347960e0003 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -37,6 +37,7 @@ mod inefficient_to_string; mod inspect_for_each; mod into_iter_on_ref; mod io_other_error; +mod ip_constant; mod is_digit_ascii_radix; mod is_empty; mod iter_cloned_collect; @@ -4528,6 +4529,42 @@ declare_clippy_lint! { "detect swap with a temporary value" } +declare_clippy_lint! { + /// ### What it does + /// Checks for IP addresses that could be replaced with predefined constants such as + /// `Ipv4Addr::new(127, 0, 0, 1)` instead of using the appropriate constants. + /// + /// ### Why is this bad? + /// Using specific IP addresses like `127.0.0.1` or `::1` is less clear and less maintainable than using the + /// predefined constants `Ipv4Addr::LOCALHOST` or `Ipv6Addr::LOCALHOST`. These constants improve code + /// readability, make the intent explicit, and are less error-prone. + /// + /// ### Example + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::new(127, 0, 0, 1); + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); + /// ``` + /// Use instead: + /// ```no_run + /// use std::net::{Ipv4Addr, Ipv6Addr}; + /// + /// // IPv4 loopback + /// let addr_v4 = Ipv4Addr::LOCALHOST; + /// + /// // IPv6 loopback + /// let addr_v6 = Ipv6Addr::LOCALHOST; + /// ``` + #[clippy::version = "1.89.0"] + pub IP_CONSTANT, + pedantic, + "hardcoded localhost IP address" +} + #[expect(clippy::struct_excessive_bools)] pub struct Methods { avoid_breaking_exported_api: bool, @@ -4706,6 +4743,7 @@ impl_lint_pass!(Methods => [ MANUAL_CONTAINS, IO_OTHER_ERROR, SWAP_WITH_TEMPORARY, + IP_CONSTANT, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4738,6 +4776,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { 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); + ip_constant::check(cx, expr, func, args); }, ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 2b75d6a8248..0075bf166cc 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -44,11 +44,10 @@ pub(super) fn check<'tcx>( if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { let mut app = Applicability::MachineApplicable; - let name = name.ident.as_str(); let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name { - "len" => { + let sugg: String = match name.ident.name { + sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( cx.tcx.get_diagnostic_name(adt.did()), @@ -60,13 +59,13 @@ pub(super) fn check<'tcx>( return; } }, - "is_empty" + sym::is_empty if is_is_empty_sig(cx, parent.hir_id) && iterates_same_ty(cx, cx.typeck_results().expr_ty(iter_expr), collect_ty) => { "next().is_none()".into() }, - "contains" => { + sym::contains => { if is_contains_sig(cx, parent.hir_id, iter_expr) && let Some(arg) = args.first() { diff --git a/clippy_lints/src/methods/open_options.rs b/clippy_lints/src/methods/open_options.rs index bce314e64f0..fd368024177 100644 --- a/clippy_lints/src/methods/open_options.rs +++ b/clippy_lints/src/methods/open_options.rs @@ -1,14 +1,14 @@ use rustc_data_structures::fx::FxHashMap; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::paths; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{paths, sym}; use rustc_ast::ast::LitKind; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; +use rustc_span::Span; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; use super::{NONSENSICAL_OPEN_OPTIONS, SUSPICIOUS_OPEN_OPTIONS}; @@ -87,23 +87,23 @@ fn get_open_options( _ => Argument::Unknown, }; - match path.ident.as_str() { - "create" => { + match path.ident.name { + sym::create => { options.push((OpenOption::Create, argument_option, span)); }, - "create_new" => { + sym::create_new => { options.push((OpenOption::CreateNew, argument_option, span)); }, - "append" => { + sym::append => { options.push((OpenOption::Append, argument_option, span)); }, - "truncate" => { + sym::truncate => { options.push((OpenOption::Truncate, argument_option, span)); }, - "read" => { + sym::read => { options.push((OpenOption::Read, argument_option, span)); }, - "write" => { + sym::write => { options.push((OpenOption::Write, argument_option, span)); }, _ => { diff --git a/clippy_lints/src/methods/str_splitn.rs b/clippy_lints/src/methods/str_splitn.rs index 6935ae1f391..6f78d6c6128 100644 --- a/clippy_lints/src/methods/str_splitn.rs +++ b/clippy_lints/src/methods/str_splitn.rs @@ -285,9 +285,9 @@ fn parse_iter_usage<'tcx>( let did = cx.typeck_results().type_dependent_def_id(e.hir_id)?; let iter_id = cx.tcx.get_diagnostic_item(sym::Iterator)?; - match (name.ident.as_str(), args) { - ("next", []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), - ("next_tuple", []) => { + match (name.ident.name, args) { + (sym::next, []) if cx.tcx.trait_of_item(did) == Some(iter_id) => (IterUsageKind::Nth(0), e.span), + (sym::next_tuple, []) => { return if paths::ITERTOOLS_NEXT_TUPLE.matches(cx, did) && let ty::Adt(adt_def, subs) = cx.typeck_results().expr_ty(e).kind() && cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) @@ -303,7 +303,7 @@ fn parse_iter_usage<'tcx>( None }; }, - ("nth" | "skip", [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { + (sym::nth | sym::skip, [idx_expr]) if cx.tcx.trait_of_item(did) == Some(iter_id) => { if let Some(Constant::Int(idx)) = ConstEvalCtxt::new(cx).eval(idx_expr) { let span = if name.ident.as_str() == "nth" { e.span diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index dbff08bc51c..edd4ecd2b4a 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -5,7 +5,8 @@ use clippy_utils::{is_trait_method, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath}; use rustc_lint::LateContext; -use rustc_middle::ty::{self, GenericArgKind}; +use rustc_middle::ty; +use rustc_middle::ty::GenericArgKind; use rustc_span::sym; use rustc_span::symbol::Ident; use std::iter; diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs index 4b32ba83b32..741f38f9756 100644 --- a/clippy_lints/src/multiple_bound_locations.rs +++ b/clippy_lints/src/multiple_bound_locations.rs @@ -47,7 +47,7 @@ impl EarlyLintPass for MultipleBoundLocations { for param in &generics.params { if !param.bounds.is_empty() { - generic_params_with_bounds.insert(param.ident.name.as_str(), param.ident.span); + generic_params_with_bounds.insert(param.ident.as_str(), param.ident.span); } } for clause in &generics.where_clause.predicates { @@ -64,7 +64,7 @@ impl EarlyLintPass for MultipleBoundLocations { }, WherePredicateKind::RegionPredicate(pred) => { if !pred.bounds.is_empty() - && let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.name.as_str()) + && let Some(bound_span) = generic_params_with_bounds.get(&pred.lifetime.ident.as_str()) { emit_lint(cx, *bound_span, pred.lifetime.ident.span); } diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 3b271ca0dc1..9b327955608 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -99,10 +99,6 @@ impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> { self.found = true; return; }, - ExprKind::If(..) => { - self.found = true; - return; - }, ExprKind::Path(_) => { if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) && adj diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index 93865197ec9..04b09276966 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -293,7 +293,14 @@ fn self_cmp_call<'tcx>( ExprKind::Call(path, [_, _]) => path_res(cx, path) .opt_def_id() .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), - ExprKind::MethodCall(_, _, [_other], ..) => { + ExprKind::MethodCall(_, recv, [_], ..) => { + let ExprKind::Path(path) = recv.kind else { + return false; + }; + if last_path_segment(&path).ident.name != kw::SelfLower { + return false; + } + // We can set this to true here no matter what as if it's a `MethodCall` and goes to the // `else` branch, it must be a method named `cmp` that isn't `Ord::cmp` *needs_fully_qualified = true; diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs index 35caac855cf..4ce6827cac9 100644 --- a/clippy_lints/src/pathbuf_init_then_push.rs +++ b/clippy_lints/src/pathbuf_init_then_push.rs @@ -1,14 +1,14 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::path_to_local_id; use clippy_utils::source::{SpanRangeExt, snippet}; use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{path_to_local_id, sym}; use rustc_ast::{LitKind, StrStyle}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, LetStmt, PatKind, QPath, Stmt, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -177,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for PathbufThenPush<'tcx> { && 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" + && name.ident.name == sym::push { searcher.err_span = searcher.err_span.to(stmt.span); searcher.arg = Some(*arg_expr); diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 9149406642d..94cdcf00054 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lin use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::Sugg; use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core}; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core, sym}; use hir::LifetimeKind; use rustc_abi::ExternAbi; use rustc_errors::{Applicability, MultiSpan}; @@ -18,8 +18,8 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; use rustc_session::declare_lint_pass; +use rustc_span::Span; use rustc_span::symbol::Symbol; -use rustc_span::{Span, sym}; use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; @@ -268,6 +268,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { (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), }; + let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; span_lint_and_sugg( cx, @@ -275,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for Ptr { expr.span, "comparing with null is better expressed by the `.is_null()` method", "try", - format!("{non_null_path_snippet}.is_null()"), + format!("{invert}{non_null_path_snippet}.is_null()",), Applicability::MachineApplicable, ); } @@ -299,7 +300,7 @@ struct PtrArg<'tcx> { emission_id: HirId, span: Span, ty_name: Symbol, - method_renames: &'static [(&'static str, &'static str)], + method_renames: &'static [(Symbol, &'static str)], ref_prefix: RefPrefix, deref_ty: DerefTy<'tcx>, } @@ -385,6 +386,7 @@ impl<'tcx> DerefTy<'tcx> { } } +#[expect(clippy::too_many_lines)] fn check_fn_args<'cx, 'tcx: 'cx>( cx: &'cx LateContext<'tcx>, fn_sig: ty::FnSig<'tcx>, @@ -409,7 +411,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id); let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) { Some(sym::Vec) => ( - [("clone", ".to_owned()")].as_slice(), + [(sym::clone, ".to_owned()")].as_slice(), DerefTy::Slice( name.args.and_then(|args| args.args.first()).and_then(|arg| { if let GenericArg::Type(ty) = arg { @@ -421,10 +423,14 @@ fn check_fn_args<'cx, 'tcx: 'cx>( args.type_at(0), ), ), - _ if Some(adt.did()) == cx.tcx.lang_items().string() => { - ([("clone", ".to_owned()"), ("as_str", "")].as_slice(), DerefTy::Str) - }, - Some(sym::PathBuf) => ([("clone", ".to_path_buf()"), ("as_path", "")].as_slice(), DerefTy::Path), + _ if Some(adt.did()) == cx.tcx.lang_items().string() => ( + [(sym::clone, ".to_owned()"), (sym::as_str, "")].as_slice(), + DerefTy::Str, + ), + Some(sym::PathBuf) => ( + [(sym::clone, ".to_path_buf()"), (sym::as_path, "")].as_slice(), + DerefTy::Path, + ), Some(sym::Cow) if mutability == Mutability::Not => { if let Some((lifetime, ty)) = name.args.and_then(|args| { if let [GenericArg::Lifetime(lifetime), ty] = args.args { @@ -595,7 +601,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ if let ExprKind::MethodCall(name, receiver, ..) = use_expr.kind && receiver.hir_id == child_id { - let name = name.ident.as_str(); + let name = name.ident.name; // Check if the method can be renamed. if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) { diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs index 49b522994fb..6b1dc864fb7 100644 --- a/clippy_lints/src/read_zero_byte_vec.rs +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; -use clippy_utils::get_enclosing_block; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; +use clippy_utils::{get_enclosing_block, sym}; use hir::{Expr, ExprKind, HirId, LetStmt, PatKind, PathSegment, QPath, StmtKind}; use rustc_errors::Applicability; @@ -87,7 +87,7 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { diag.span_suggestion( expr.span, "try", - format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")), + format!("{}.resize({len}, 0); {}", ident, snippet(cx, expr.span, "..")), applicability, ); }, @@ -106,7 +106,7 @@ impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { "try", format!( "{}.resize({}, 0); {}", - ident.as_str(), + ident, snippet(cx, e.span, ".."), snippet(cx, expr.span, "..") ), @@ -142,8 +142,8 @@ impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> { if let ExprKind::MethodCall(path, receiver, args, _) = e.kind { let PathSegment { ident, .. } = *path; - match ident.as_str() { - "read" | "read_exact" => { + match ident.name { + sym::read | sym::read_exact => { let [arg] = args else { return }; if let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind @@ -155,7 +155,7 @@ impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> { return; } }, - "resize" => { + sym::resize => { // If the Vec is resized, then it's a valid read if let ExprKind::Path(QPath::Resolved(_, inner_path)) = receiver.kind && let Res::Local(res_id) = inner_path.res diff --git a/clippy_lints/src/reserve_after_initialization.rs b/clippy_lints/src/reserve_after_initialization.rs index 152d7450f5f..51adbbcd58b 100644 --- a/clippy_lints/src/reserve_after_initialization.rs +++ b/clippy_lints/src/reserve_after_initialization.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; -use clippy_utils::{is_from_proc_macro, path_to_local_id}; +use clippy_utils::{is_from_proc_macro, path_to_local_id, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, LetStmt, PatKind, QPath, Stmt, StmtKind}; @@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for ReserveAfterInitialization { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [space_hint], _) = expr.kind && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "reserve" + && name.ident.name == sym::reserve && !is_from_proc_macro(cx, expr) { self.searcher = Some(VecReserveSearcher { diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index d85f4a8fa01..f6c128d4c52 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -143,7 +143,7 @@ impl LateLintPass<'_> for SemicolonBlock { StmtKind::Expr(Expr { kind: ExprKind::Block(block, _), .. - }) if !block.span.from_expansion() => { + }) if !block.span.from_expansion() && stmt.span.contains(block.span) => { let Block { expr: None, stmts: [.., stmt], diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index a64b9b22378..b36a5d6d502 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::paths; +use clippy_utils::{paths, sym}; use rustc_hir::{Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -36,9 +36,9 @@ impl<'tcx> LateLintPass<'tcx> for SerdeApi { 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), + match item.ident.name { + sym::visit_str => seen_str = Some(item.span), + sym::visit_string => seen_string = Some(item.span), _ => {}, } } diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 3d39386ecf9..442b3250d86 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -1,13 +1,13 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_from_proc_macro; use clippy_utils::msrvs::Msrv; use rustc_attr_data_structures::{StabilityLevel, StableSince}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::DefId; -use rustc_hir::{HirId, Path, PathSegment}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_hir::{Block, Body, HirId, Path, PathSegment}; +use rustc_lint::{LateContext, LateLintPass, Lint, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::symbol::kw; use rustc_span::{Span, sym}; @@ -88,24 +88,35 @@ declare_clippy_lint! { } pub struct StdReexports { - // Paths which can be either a module or a macro (e.g. `std::env`) will cause this check to happen - // twice. First for the mod, second for the macro. This is used to avoid the lint reporting for the macro - // when the path could be also be used to access the module. - prev_span: Span, + lint_point: (Span, Option<LintPoint>), msrv: Msrv, } impl StdReexports { pub fn new(conf: &'static Conf) -> Self { Self { - prev_span: Span::default(), + lint_point: Default::default(), msrv: conf.msrv, } } + + fn lint_if_finish(&mut self, cx: &LateContext<'_>, (span, item): (Span, Option<LintPoint>)) { + if span.source_equal(self.lint_point.0) { + return; + } + + if !self.lint_point.0.is_dummy() { + emit_lints(cx, &self.lint_point); + } + + self.lint_point = (span, item); + } } impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); +type LintPoint = (&'static Lint, &'static str, &'static str); + impl<'tcx> LateLintPass<'tcx> for StdReexports { fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { if let Res::Def(_, def_id) = path.res @@ -119,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { sym::core => (STD_INSTEAD_OF_CORE, "std", "core"), sym::alloc => (STD_INSTEAD_OF_ALLOC, "std", "alloc"), _ => { - self.prev_span = first_segment.ident.span; + self.lint_if_finish(cx, (first_segment.ident.span, None)); return; }, }, @@ -127,32 +138,44 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { if cx.tcx.crate_name(def_id.krate) == sym::core { (ALLOC_INSTEAD_OF_CORE, "alloc", "core") } else { - self.prev_span = first_segment.ident.span; + self.lint_if_finish(cx, (first_segment.ident.span, None)); return; } }, _ => return, }; - if first_segment.ident.span != self.prev_span { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - lint, - first_segment.ident.span, - format!("used import from `{used_mod}` instead of `{replace_with}`"), - |diag| { - diag.span_suggestion( - first_segment.ident.span, - format!("consider importing the item from `{replace_with}`"), - replace_with.to_string(), - Applicability::MachineApplicable, - ); - }, - ); - self.prev_span = first_segment.ident.span; - } + + self.lint_if_finish(cx, (first_segment.ident.span, Some((lint, used_mod, replace_with)))); } } + + fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &Block<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, _: &Body<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } + + fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { + self.lint_if_finish(cx, Default::default()); + } +} + +fn emit_lints(cx: &LateContext<'_>, (span, item): &(Span, Option<LintPoint>)) { + let Some((lint, used_mod, replace_with)) = item else { + return; + }; + + span_lint_and_sugg( + cx, + lint, + *span, + format!("used import from `{used_mod}` instead of `{replace_with}`"), + format!("consider importing the item from `{replace_with}`"), + (*replace_with).to_string(), + Applicability::MachineApplicable, + ); } /// Returns the first named segment of a [`Path`]. diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 73a9fe71e00..1cda6f596f4 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -560,7 +560,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { && let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id) && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind - && let trim_fn_name @ ("trim" | "trim_start" | "trim_end") = path.ident.name.as_str() + && let trim_fn_name @ (sym::trim | sym::trim_start | sym::trim_end) = path.ident.name && let Some(trim_def_id) = tyckres.type_dependent_def_id(split_recv.hir_id) && is_one_of_trim_diagnostic_items(cx, trim_def_id) { diff --git a/clippy_lints/src/transmute/missing_transmute_annotations.rs b/clippy_lints/src/transmute/missing_transmute_annotations.rs index 96286fcf73d..08f36a2ed5d 100644 --- a/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -74,7 +74,7 @@ pub(super) fn check<'tcx>( last.ident.span.with_hi(path.span.hi()), "transmute used without annotations", "consider adding missing annotations", - format!("{}::<{from_ty}, {to_ty}>", last.ident.as_str()), + format!("{}::<{from_ty}, {to_ty}>", last.ident), Applicability::MaybeIncorrect, ); true diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index 004ad03e708..4b23367645e 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -33,7 +33,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m let ltopt = if lt.is_anonymous() { String::new() } else { - format!("{} ", lt.ident.as_str()) + format!("{} ", lt.ident) }; if mut_ty.mutbl == Mutability::Mut { diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 5e1cb9e54f5..12cc1093899 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, paths, peel_blocks}; +use clippy_utils::{is_res_lang_ctor, paths, peel_blocks, sym}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -232,8 +232,16 @@ fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { while let ExprKind::MethodCall(path, receiver, ..) = expr.kind { if matches!( - path.ident.as_str(), - "unwrap" | "expect" | "unwrap_or" | "unwrap_or_else" | "ok" | "is_ok" | "is_err" | "or_else" | "or" + path.ident.name, + sym::unwrap + | sym::expect + | sym::unwrap_or + | sym::unwrap_or_else + | sym::ok + | sym::is_ok + | sym::is_err + | sym::or_else + | sym::or ) { expr = receiver; } else { diff --git a/clippy_lints/src/unused_result_ok.rs b/clippy_lints/src/unused_result_ok.rs index 958f19d1833..f5ed10fb760 100644 --- a/clippy_lints/src/unused_result_ok.rs +++ b/clippy_lints/src/unused_result_ok.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_context; +use clippy_utils::sym; use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::declare_lint_pass; -use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -36,7 +36,7 @@ impl LateLintPass<'_> for UnusedResultOk { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { if let StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(ok_path, recv, [], ..) = expr.kind //check is expr.ok() has type Result<T,E>.ok(, _) - && ok_path.ident.as_str() == "ok" + && ok_path.ident.name == sym::ok && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result) && !stmt.span.in_external_macro(cx.sess().source_map()) { diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index 9859ddfdf7b..3811f0fe6b5 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -100,7 +100,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { let segments = &poly.trait_ref.path.segments; if segments.len() == 1 - && ["Fn", "FnMut", "FnOnce"].contains(&segments[0].ident.name.as_str()) + && matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce) && let Some(args) = segments[0].args && args.parenthesized == GenericArgsParentheses::ParenSugar && let constraints = &args.constraints @@ -109,6 +109,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind && args.span_ext.hi() != poly.span.hi() && !hir_ty.span.from_expansion() + && args.span_ext.hi() != hir_ty.span.hi() && is_unit_ty(hir_ty) { lint_unneeded_unit_return(cx, hir_ty.span, poly.span); diff --git a/clippy_lints/src/vec_init_then_push.rs b/clippy_lints/src/vec_init_then_push.rs index 3c23662e9d1..8d873536295 100644 --- a/clippy_lints/src/vec_init_then_push.rs +++ b/clippy_lints/src/vec_init_then_push.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{get_parent_expr, path_to_local_id}; +use clippy_utils::{get_parent_expr, path_to_local_id, sym}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -202,7 +202,7 @@ impl<'tcx> LateLintPass<'tcx> for VecInitThenPush { if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = stmt.kind && let ExprKind::MethodCall(name, self_arg, [_], _) = expr.kind && path_to_local_id(self_arg, searcher.local_id) - && name.ident.as_str() == "push" + && name.ident.name == sym::push { self.searcher = Some(VecPushSearcher { err_span: searcher.err_span.to(stmt.span), diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 467811c586b..d9dda6eadb2 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -9,8 +9,8 @@ use rustc_hir::{Item, ItemKind, PathSegment, UseKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty; use rustc_session::impl_lint_pass; +use rustc_span::BytePos; use rustc_span::symbol::kw; -use rustc_span::{BytePos, sym}; declare_clippy_lint! { /// ### What it does @@ -193,9 +193,7 @@ impl WildcardImports { // Allow "...prelude::..::*" imports. // Many crates have a prelude, and it is imported as a glob by design. fn is_prelude_import(segments: &[PathSegment<'_>]) -> bool { - segments - .iter() - .any(|ps| ps.ident.as_str().contains(sym::prelude.as_str())) + segments.iter().any(|ps| ps.ident.as_str().contains("prelude")) } // Allow "super::*" imports in tests. diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index a8758b65c91..d9a007635ca 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -5,8 +5,8 @@ use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; use clippy_utils::{is_in_test, sym}; use rustc_ast::token::LitKind; use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, - FormatTrait, + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, }; use rustc_errors::Applicability; use rustc_hir::{Expr, Impl, Item, ItemKind}; @@ -556,12 +556,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { // Decrement the index of the remaining by the number of replaced positional arguments if !suggestion.is_empty() { for piece in &format_args.template { - if let Some((span, index)) = positional_arg_piece_span(piece) - && suggestion.iter().all(|(s, _)| *s != span) - { - let decrement = replaced_position.iter().filter(|i| **i < index).count(); - suggestion.push((span, format!("{{{}}}", index.saturating_sub(decrement)))); - } + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); } } @@ -574,7 +569,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { } } -/// Extract Span and its index from the given `piece`, iff it's positional argument. +/// Extract Span and its index from the given `piece`, if it's positional argument. fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { match piece { FormatArgsPiece::Placeholder(FormatPlaceholder { @@ -591,6 +586,57 @@ fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { } } +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option<FormatCount>, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + /// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw /// /// `r#"a"#` -> (`a`, true) diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index 09f1084fe70..6ab94a52210 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -5,7 +5,7 @@ use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; -use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_local}; +use rustc_hir::intravisit::{Visitor, walk_block, walk_expr}; use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -69,8 +69,9 @@ impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { let mut vis = WaitFinder { cx, local_id, + create_id: expr.hir_id, body_id: cx.tcx.hir_enclosing_body_owner(expr.hir_id), - state: VisitorState::WalkUpToLocal, + state: VisitorState::WalkUpToCreate, early_return: None, missing_wait_branch: None, }; @@ -131,6 +132,7 @@ struct MaybeWait(Span); struct WaitFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, local_id: HirId, + create_id: HirId, body_id: LocalDefId, state: VisitorState, early_return: Option<Span>, @@ -141,8 +143,8 @@ struct WaitFinder<'a, 'tcx> { #[derive(PartialEq)] enum VisitorState { - WalkUpToLocal, - LocalFound, + WalkUpToCreate, + CreateFound, } #[derive(Copy, Clone)] @@ -155,19 +157,13 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; type Result = ControlFlow<MaybeWait>; - fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result { - if self.state == VisitorState::WalkUpToLocal - && let PatKind::Binding(_, pat_id, ..) = l.pat.kind - && self.local_id == pat_id - { - self.state = VisitorState::LocalFound; + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { + if ex.hir_id == self.create_id { + self.state = VisitorState::CreateFound; + return Continue(()); } - walk_local(self, l) - } - - fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) -> Self::Result { - if self.state != VisitorState::LocalFound { + if self.state != VisitorState::CreateFound { return walk_expr(self, ex); } |
