diff options
| author | bors <bors@rust-lang.org> | 2025-09-05 02:01:50 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2025-09-05 02:01:50 +0000 |
| commit | 91edc3ebccc4daa46c20a93f4709862376da1fdd (patch) | |
| tree | 63e75231cb63d011d54d9cd3e771fb4b91eb81da /src/tools/clippy/clippy_lints | |
| parent | b3cfb8faf84c5f3b7909479a9f9b6a3290d5f4d7 (diff) | |
| parent | 75b9ee5f085f695cba061fd9ed7fa3b912dd1bbf (diff) | |
| download | rust-91edc3ebccc4daa46c20a93f4709862376da1fdd.tar.gz rust-91edc3ebccc4daa46c20a93f4709862376da1fdd.zip | |
Auto merge of #146218 - flip1995:clippy-subtree-update, r=Manishearth
Clippy subtree update r? `@Manishearth`
Diffstat (limited to 'src/tools/clippy/clippy_lints')
77 files changed, 1383 insertions, 948 deletions
diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml index b9e6de79cc0..70184ee2ca5 100644 --- a/src/tools/clippy/clippy_lints/Cargo.toml +++ b/src/tools/clippy/clippy_lints/Cargo.toml @@ -27,6 +27,10 @@ url = "2.2" [dev-dependencies] walkdir = "2.3" +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = ['cfg(bootstrap)'] + [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] rustc_private = true diff --git a/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs b/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs index 6f2a6a36a38..08253b0c499 100644 --- a/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs +++ b/src/tools/clippy/clippy_lints/src/assertions_on_result_states.rs @@ -3,10 +3,10 @@ use clippy_utils::macros::{PanicExpn, find_assert_args, root_macro_call_first_no use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item}; use clippy_utils::usage::local_used_after_expr; -use clippy_utils::{is_expr_final_block_expr, path_res, sym}; +use clippy_utils::{path_res, sym}; use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind}; +use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::declare_lint_pass; @@ -77,17 +77,20 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { _ => return, }; span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| { - let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" }; let mut app = Applicability::MachineApplicable; - diag.span_suggestion( - macro_call.span, - "replace with", - format!( - "{}.{replacement}(){semicolon}", - snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 - ), - app, - ); + let recv = snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0; + + // `assert!` doesn't return anything, but `Result::unwrap(_err)` does, so we might need to add a + // semicolon to the suggestion to avoid leaking the type + let sugg = match cx.tcx.parent_hir_node(e.hir_id) { + // trailing expr of a block + Node::Block(..) => format!("{recv}.{replacement}();"), + // already has a trailing semicolon + Node::Stmt(..) => format!("{recv}.{replacement}()"), + // this is the last-resort option, because it's rather verbose + _ => format!("{{ {recv}.{replacement}(); }}"), + }; + diag.span_suggestion(macro_call.span, "replace with", sugg, app); }); } } diff --git a/src/tools/clippy/clippy_lints/src/async_yields_async.rs b/src/tools/clippy/clippy_lints/src/async_yields_async.rs index 013819b0da8..1a10db291cd 100644 --- a/src/tools/clippy/clippy_lints/src/async_yields_async.rs +++ b/src/tools/clippy/clippy_lints/src/async_yields_async.rs @@ -1,8 +1,12 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::source::snippet; +use clippy_utils::is_expr_async_block; +use clippy_utils::source::walk_span_to_context; +use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; -use rustc_hir::{Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath}; +use rustc_hir::{ + Block, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -87,31 +91,37 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { let expr_ty = typeck_results.expr_ty(body_expr); if implements_trait(cx, expr_ty, future_trait_def_id, &[]) { - let return_expr_span = match &body_expr.kind { - // XXXkhuey there has to be a better way. - ExprKind::Block(block, _) => block.expr.map(|e| e.span), - ExprKind::Path(QPath::Resolved(_, path)) => Some(path.span), - _ => None, + let (return_expr, return_expr_span) = match &body_expr.kind { + ExprKind::Block(Block { expr: Some(e), .. }, _) => (*e, e.span), + ExprKind::Path(QPath::Resolved(_, path)) => (body_expr, path.span), + _ => return, }; - if let Some(return_expr_span) = return_expr_span { - span_lint_hir_and_then( - cx, - ASYNC_YIELDS_ASYNC, - body_expr.hir_id, - return_expr_span, - "an async construct yields a type which is itself awaitable", - |db| { - db.span_label(body_expr.span, "outer async construct"); - db.span_label(return_expr_span, "awaitable value not awaited"); - db.span_suggestion( - return_expr_span, - "consider awaiting this value", - format!("{}.await", snippet(cx, return_expr_span, "..")), - Applicability::MaybeIncorrect, - ); - }, - ); + + let return_expr_span = walk_span_to_context(return_expr_span, expr.span.ctxt()).unwrap_or(return_expr_span); + let mut applicability = Applicability::MaybeIncorrect; + let mut return_expr_snip = + Sugg::hir_with_context(cx, return_expr, expr.span.ctxt(), "..", &mut applicability); + if !is_expr_async_block(return_expr) { + return_expr_snip = return_expr_snip.maybe_paren(); } + + span_lint_hir_and_then( + cx, + ASYNC_YIELDS_ASYNC, + body_expr.hir_id, + return_expr_span, + "an async construct yields a type which is itself awaitable", + |db| { + db.span_label(body_expr.span, "outer async construct"); + db.span_label(return_expr_span, "awaitable value not awaited"); + db.span_suggestion( + return_expr_span, + "consider awaiting this value", + format!("{return_expr_snip}.await"), + applicability, + ); + }, + ); } } } diff --git a/src/tools/clippy/clippy_lints/src/bool_comparison.rs b/src/tools/clippy/clippy_lints/src/bool_comparison.rs new file mode 100644 index 00000000000..722095909a6 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/bool_comparison.rs @@ -0,0 +1,179 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_expn_of, peel_blocks, sym}; +use rustc_ast::ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; +use rustc_span::source_map::Spanned; + +declare_clippy_lint! { + /// ### What it does + /// Checks for expressions of the form `x == true`, + /// `x != true` and order comparisons such as `x < true` (or vice versa) and + /// suggest using the variable directly. + /// + /// ### Why is this bad? + /// Unnecessary code. + /// + /// ### Example + /// ```rust,ignore + /// if x == true {} + /// if y == false {} + /// ``` + /// use `x` directly: + /// ```rust,ignore + /// if x {} + /// if !y {} + /// ``` + #[clippy::version = "pre 1.29.0"] + pub BOOL_COMPARISON, + complexity, + "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" +} + +declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); + +impl<'tcx> LateLintPass<'tcx> for BoolComparison { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if e.span.from_expansion() { + return; + } + + if let ExprKind::Binary(Spanned { node, .. }, left_side, right_side) = e.kind + && is_expn_of(left_side.span, sym::cfg).is_none() + && is_expn_of(right_side.span, sym::cfg).is_none() + && cx.typeck_results().expr_ty(left_side).is_bool() + && cx.typeck_results().expr_ty(right_side).is_bool() + { + let ignore_case = None::<(fn(_) -> _, &str)>; + let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; + match node { + BinOpKind::Eq => { + let true_case = Some((|h| h, "equality checks against true are unnecessary")); + let false_case = Some(( + |h: Sugg<'tcx>| !h, + "equality checks against false can be replaced by a negation", + )); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Ne => { + let true_case = Some(( + |h: Sugg<'tcx>| !h, + "inequality checks against true can be replaced by a negation", + )); + let false_case = Some((|h| h, "inequality checks against false are unnecessary")); + check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); + }, + BinOpKind::Lt => check_comparison( + cx, + e, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), + "order comparisons between booleans can be simplified", + )), + ), + BinOpKind::Gt => check_comparison( + cx, + e, + Some(( + |h: Sugg<'tcx>| !h, + "less than comparison against true can be replaced by a negation", + )), + ignore_case, + ignore_case, + Some((|h| h, "greater than checks against false are unnecessary")), + Some(( + |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), + "order comparisons between booleans can be simplified", + )), + ), + _ => (), + } + } + } +} + +fn check_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'_>, + left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, + no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, +) { + if let ExprKind::Binary(_, left_side, right_side) = e.kind { + let mut applicability = Applicability::MachineApplicable; + // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, + // calling `source_callsite` make sure macros are handled correctly, see issue #9907 + let binop_span = left_side.span.source_callsite().to(right_side.span.source_callsite()); + + match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { + (Some(true), None) => left_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(true)) => right_true.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (Some(false), None) => left_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); + }), + (None, Some(false)) => right_false.map_or((), |(h, m)| { + suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); + }), + (None, None) => no_literal.map_or((), |(h, m)| { + let left_side = Sugg::hir_with_context(cx, left_side, binop_span.ctxt(), "..", &mut applicability); + let right_side = Sugg::hir_with_context(cx, right_side, binop_span.ctxt(), "..", &mut applicability); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + binop_span, + m, + "try", + h(left_side, right_side).into_string(), + applicability, + ); + }), + _ => (), + } + } +} + +fn suggest_bool_comparison<'a, 'tcx>( + cx: &LateContext<'tcx>, + span: Span, + expr: &Expr<'_>, + mut app: Applicability, + message: &'static str, + conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, +) { + let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + BOOL_COMPARISON, + span, + message, + "try", + conv_hint(hint).into_string(), + app, + ); +} + +fn fetch_bool_expr(expr: &Expr<'_>) -> Option<bool> { + if let ExprKind::Lit(lit_ptr) = peel_blocks(expr).kind + && let LitKind::Bool(value) = lit_ptr.node + { + return Some(value); + } + None +} diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs index a1543cabd2f..d78da9396fa 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -8,17 +8,12 @@ use rustc_middle::ty::{self, Ty}; use super::CAST_PTR_ALIGNMENT; -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::Cast(cast_expr, cast_to) = expr.kind { - if is_hir_ty_cfg_dependant(cx, cast_to) { - return; - } - let (cast_from, cast_to) = ( - cx.typeck_results().expr_ty(cast_expr), - cx.typeck_results().expr_ty(expr), - ); - lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); - } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>) { + lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); +} + +pub(super) fn check_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind && method_path.ident.name == sym::cast && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args @@ -74,14 +69,13 @@ fn is_used_as_unaligned(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { ExprKind::Call(func, [arg, ..]) if arg.hir_id == e.hir_id => { if let ExprKind::Path(path) = &func.kind && let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) && matches!( - cx.tcx.get_diagnostic_name(def_id), - Some( - sym::ptr_write_unaligned - | sym::ptr_read_unaligned - | sym::intrinsics_unaligned_volatile_load - | sym::intrinsics_unaligned_volatile_store - ) + name, + sym::ptr_write_unaligned + | sym::ptr_read_unaligned + | sym::intrinsics_unaligned_volatile_load + | sym::intrinsics_unaligned_volatile_store ) { true diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs index 46b0c88d3fe..bce7b4c69cc 100644 --- a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs +++ b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs @@ -1,10 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; +use clippy_utils::{get_parent_expr, is_no_std_crate}; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; use rustc_hir::{Expr, ExprKind}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; @@ -42,13 +44,52 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>, let mut applicability = Applicability::MachineApplicable; let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; span_lint_and_sugg( cx, CAST_SLICE_FROM_RAW_PARTS, span, format!("casting the result of `{func}` to {cast_to}"), "replace with", - format!("core::ptr::slice_{func}({ptr}, {len})"), + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), + applicability, + ); + } +} + +/// Checks for implicit cast from slice reference to raw slice pointer. +pub(super) fn check_implicit_cast(cx: &LateContext<'_>, expr: &Expr<'_>) { + if let ExprKind::Call(fun, [ptr_arg, len_arg]) = expr.peel_blocks().kind + && let ExprKind::Path(ref qpath) = fun.kind + && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() + && let Some(rpk) = raw_parts_kind(cx, fun_def_id) + && !matches!(get_parent_expr(cx, expr).map(|e| e.kind), Some(ExprKind::Cast(..))) + && let [deref, borrow] = cx.typeck_results().expr_adjustments(expr) + && matches!(deref.kind, Adjust::Deref(..)) + && let Adjustment { + kind: Adjust::Borrow(AutoBorrow::RawPtr(..)), + target, + } = borrow + && let ty::RawPtr(pointee_ty, _) = target.kind() + && pointee_ty.is_slice() + && !expr.span.from_expansion() + { + let func = match rpk { + RawPartsKind::Immutable => "from_raw_parts", + RawPartsKind::Mutable => "from_raw_parts_mut", + }; + let mut applicability = Applicability::MachineApplicable; + let ctxt = expr.span.ctxt(); + let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0; + let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0; + let krate = if is_no_std_crate(cx) { "core" } else { "std" }; + span_lint_and_sugg( + cx, + CAST_SLICE_FROM_RAW_PARTS, + expr.span, + format!("implicitly casting the result of `{func}` to `{target}`"), + "replace_with", + format!("{krate}::ptr::slice_{func}({ptr}, {len})"), applicability, ); } diff --git a/src/tools/clippy/clippy_lints/src/casts/mod.rs b/src/tools/clippy/clippy_lints/src/casts/mod.rs index e25df9dd249..d2e62ee56e4 100644 --- a/src/tools/clippy/clippy_lints/src/casts/mod.rs +++ b/src/tools/clippy/clippy_lints/src/casts/mod.rs @@ -873,7 +873,9 @@ impl<'tcx> LateLintPass<'tcx> for Casts { } char_lit_as_u8::check(cx, expr, cast_from_expr, cast_to); cast_slice_from_raw_parts::check(cx, expr, cast_from_expr, cast_to, self.msrv); + cast_ptr_alignment::check(cx, expr, cast_from, cast_to); ptr_cast_constness::check(cx, expr, cast_from_expr, cast_from, cast_to, self.msrv); + ptr_as_ptr::check(cx, expr, cast_from_expr, cast_from, cast_to_hir, cast_to, self.msrv); as_ptr_cast_mut::check(cx, expr, cast_from_expr, cast_to); fn_to_numeric_cast_any::check(cx, expr, cast_from_expr, cast_from, cast_to); confusing_method_to_numeric_cast::check(cx, expr, cast_from_expr, cast_from, cast_to); @@ -911,8 +913,10 @@ impl<'tcx> LateLintPass<'tcx> for Casts { if self.msrv.meets(cx, msrvs::RAW_REF_OP) { borrow_as_ptr::check_implicit_cast(cx, expr); } - cast_ptr_alignment::check(cx, expr); - ptr_as_ptr::check(cx, expr, self.msrv); + if self.msrv.meets(cx, msrvs::PTR_SLICE_RAW_PARTS) { + cast_slice_from_raw_parts::check_implicit_cast(cx, expr); + } + cast_ptr_alignment::check_cast_method(cx, expr); cast_slice_different_sizes::check(cx, expr, self.msrv); ptr_cast_constness::check_null_ptr_cast_method(cx, expr); } diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs index 89075409098..d012380cb76 100644 --- a/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_as_ptr.rs @@ -4,9 +4,9 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath, TyKind}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty; +use rustc_middle::ty::{self, Ty}; use rustc_span::{Span, sym}; use super::PTR_AS_PTR; @@ -26,13 +26,18 @@ impl OmitFollowedCastReason<'_> { } } -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) { - if let ExprKind::Cast(cast_expr, cast_to_hir_ty) = expr.kind - && let (cast_from, cast_to) = (cx.typeck_results().expr_ty(cast_expr), cx.typeck_results().expr_ty(expr)) - && let ty::RawPtr(_, from_mutbl) = cast_from.kind() +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'tcx>, + cast_from_expr: &Expr<'_>, + cast_from: Ty<'_>, + cast_to_hir: &hir::Ty<'_>, + cast_to: Ty<'tcx>, + msrv: Msrv, +) { + if let ty::RawPtr(_, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind() - && matches!((from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Not) | (Mutability::Mut, Mutability::Mut)) + && from_mutbl == to_mutbl // The `U` in `pointer::cast` have to be `Sized` // as explained here: https://github.com/rust-lang/rust/issues/60602. && to_pointee_ty.is_sized(cx.tcx, cx.typing_env()) @@ -40,7 +45,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) && !is_from_proc_macro(cx, expr) { let mut app = Applicability::MachineApplicable; - let turbofish = match &cast_to_hir_ty.kind { + let turbofish = match &cast_to_hir.kind { TyKind::Infer(()) => String::new(), TyKind::Ptr(mut_ty) => { if matches!(mut_ty.ty.kind, TyKind::Infer(())) { @@ -58,16 +63,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) // following `cast` does not compile because it fails to infer what type is expected // as type argument to `std::ptr::ptr_null` or `std::ptr::ptr_null_mut`, so // we omit following `cast`: - let omit_cast = if let ExprKind::Call(func, []) = cast_expr.kind + let omit_cast = if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind && let Some(method_defid) = path.res.opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::ptr_null, method_defid) { - OmitFollowedCastReason::Null(qpath) - } else if cx.tcx.is_diagnostic_item(sym::ptr_null_mut, method_defid) { - OmitFollowedCastReason::NullMut(qpath) - } else { - OmitFollowedCastReason::None + match cx.tcx.get_diagnostic_name(method_defid) { + Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath), + Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath), + _ => OmitFollowedCastReason::None, } } else { OmitFollowedCastReason::None @@ -78,7 +81,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, msrv: Msrv) let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app); ("try call directly", format!("{method}{turbofish}()")) } else { - let cast_expr_sugg = Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app); + let cast_expr_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app); ( "try `pointer::cast`, a safer alternative", diff --git a/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs b/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs index c0c0a47f855..c0f13f5e578 100644 --- a/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs +++ b/src/tools/clippy/clippy_lints/src/casts/ptr_cast_constness.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Mutability, QPath}; +use rustc_hir::{self as hir, Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty::{self, Ty, TypeVisitableExt}; @@ -12,26 +13,23 @@ use super::PTR_CAST_CONSTNESS; pub(super) fn check<'tcx>( cx: &LateContext<'_>, expr: &Expr<'_>, - cast_expr: &Expr<'_>, + cast_from_expr: &Expr<'_>, cast_from: Ty<'tcx>, cast_to: Ty<'tcx>, msrv: Msrv, ) { if let ty::RawPtr(from_ty, from_mutbl) = cast_from.kind() && let ty::RawPtr(to_ty, to_mutbl) = cast_to.kind() - && matches!( - (from_mutbl, to_mutbl), - (Mutability::Not, Mutability::Mut) | (Mutability::Mut, Mutability::Not) - ) + && from_mutbl != to_mutbl && from_ty == to_ty && !from_ty.has_erased_regions() { - if let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") && let Some((source, target, target_func)) = match cx.tcx.get_diagnostic_name(defid) { Some(sym::ptr_null) => Some(("const", "mutable", "null_mut")), @@ -53,11 +51,17 @@ pub(super) fn check<'tcx>( if msrv.meets(cx, msrvs::POINTER_CAST_CONSTNESS) { let mut app = Applicability::MachineApplicable; - let sugg = Sugg::hir_with_context(cx, cast_expr, expr.span.ctxt(), "_", &mut app); - let constness = match *to_mutbl { - Mutability::Not => "const", - Mutability::Mut => "mut", + let sugg = if let ExprKind::Cast(nested_from, nested_hir_ty) = cast_from_expr.kind + && let hir::TyKind::Ptr(ptr_ty) = nested_hir_ty.kind + && let hir::TyKind::Infer(()) = ptr_ty.ty.kind + { + // `(foo as *const _).cast_mut()` fails method name resolution + // avoid this by `as`-ing the full type + Sugg::hir_with_context(cx, nested_from, expr.span.ctxt(), "_", &mut app).as_ty(cast_from) + } else { + Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app) }; + let constness = to_mutbl.ptr_str(); span_lint_and_sugg( cx, @@ -73,8 +77,8 @@ pub(super) fn check<'tcx>( } pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) { - if let ExprKind::MethodCall(method, cast_expr, [], _) = expr.kind - && let ExprKind::Call(func, []) = cast_expr.kind + if let ExprKind::MethodCall(method, cast_from_expr, [], _) = expr.kind + && let ExprKind::Call(func, []) = cast_from_expr.kind && let ExprKind::Path(QPath::Resolved(None, path)) = func.kind && let Some(defid) = path.res.opt_def_id() && let method = match (cx.tcx.get_diagnostic_name(defid), method.ident.name) { @@ -84,7 +88,7 @@ pub(super) fn check_null_ptr_cast_method(cx: &LateContext<'_>, expr: &Expr<'_>) } && let Some(prefix) = std_or_core(cx) && let mut app = Applicability::MachineApplicable - && let sugg = format!("{}", Sugg::hir_with_applicability(cx, cast_expr, "_", &mut app)) + && let sugg = snippet_with_applicability(cx, cast_from_expr.span, "_", &mut app) && let Some((_, after_lt)) = sugg.split_once("::<") { span_lint_and_sugg( diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs index 8f95c63a854..7646aa48b77 100644 --- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs +++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs @@ -23,8 +23,8 @@ declare_clippy_lint! { /// /// ### Known problems /// The true Cognitive Complexity of a method is not something we can - /// calculate using modern technology. This lint has been left in the - /// `nursery` so as to not mislead users into using this lint as a + /// calculate using modern technology. This lint has been left in + /// `restriction` so as to not mislead users into using this lint as a /// measurement tool. /// /// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793) diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs index e67e8d9070f..d0c7443a4a4 100644 --- a/src/tools/clippy/clippy_lints/src/declared_lints.rs +++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs @@ -35,6 +35,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::await_holding_invalid::AWAIT_HOLDING_REFCELL_REF_INFO, crate::blocks_in_conditions::BLOCKS_IN_CONDITIONS_INFO, crate::bool_assert_comparison::BOOL_ASSERT_COMPARISON_INFO, + crate::bool_comparison::BOOL_COMPARISON_INFO, crate::bool_to_int_with_if::BOOL_TO_INT_WITH_IF_INFO, crate::booleans::NONMINIMAL_BOOL_INFO, crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, @@ -538,7 +539,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::mutex_atomic::MUTEX_ATOMIC_INFO, crate::mutex_atomic::MUTEX_INTEGER_INFO, crate::needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE_INFO, - crate::needless_bool::BOOL_COMPARISON_INFO, crate::needless_bool::NEEDLESS_BOOL_INFO, crate::needless_bool::NEEDLESS_BOOL_ASSIGN_INFO, crate::needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE_INFO, diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs index 7580d6cab66..06c2393e0a3 100644 --- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs +++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs @@ -10,7 +10,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::adjustment::{Adjust, PointerCoercion}; -use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults}; +use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty, TypeckResults, VariantDef}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -85,6 +85,13 @@ fn contains_trait_object(ty: Ty<'_>) -> bool { } } +fn determine_derive_macro(cx: &LateContext<'_>, is_const: bool) -> Option<&'static str> { + (!is_const) + .then_some("derive") + .or_else(|| cx.tcx.features().enabled(sym::derive_const).then_some("derive_const")) +} + +#[expect(clippy::too_many_arguments)] fn check_struct<'tcx>( cx: &LateContext<'tcx>, item: &'tcx Item<'_>, @@ -93,6 +100,7 @@ fn check_struct<'tcx>( adt_def: AdtDef<'_>, ty_args: GenericArgsRef<'_>, typeck_results: &'tcx TypeckResults<'tcx>, + is_const: bool, ) { if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind && let Some(PathSegment { args, .. }) = p.segments.last() @@ -125,14 +133,18 @@ fn check_struct<'tcx>( ExprKind::Tup(fields) => fields.iter().all(is_default_without_adjusts), ExprKind::Call(callee, args) if is_path_self(callee) => args.iter().all(is_default_without_adjusts), ExprKind::Struct(_, fields, _) => fields.iter().all(|ef| is_default_without_adjusts(ef.expr)), - _ => false, + _ => return, }; - if should_emit { + if should_emit && let Some(derive_snippet) = determine_derive_macro(cx, is_const) { let struct_span = cx.tcx.def_span(adt_def.did()); + let indent_enum = indent_of(cx, struct_span).unwrap_or(0); let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation - (struct_span.shrink_to_lo(), "#[derive(Default)]\n".to_string()), // Add the derive attribute + ( + struct_span.shrink_to_lo(), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), + ), // Add the derive attribute ]; span_lint_and_then(cx, DERIVABLE_IMPLS, item.span, "this `impl` can be derived", |diag| { @@ -145,11 +157,41 @@ fn check_struct<'tcx>( } } -fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Expr<'_>, adt_def: AdtDef<'_>) { - if let ExprKind::Path(QPath::Resolved(None, p)) = &peel_blocks(func_expr).kind - && let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res - && let variant_id = cx.tcx.parent(id) - && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) +fn extract_enum_variant<'tcx>( + cx: &LateContext<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, +) -> Option<&'tcx VariantDef> { + match &peel_blocks(func_expr).kind { + ExprKind::Path(QPath::Resolved(None, p)) + if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res + && let variant_id = cx.tcx.parent(id) + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.def_id == variant_id) => + { + Some(variant_def) + }, + ExprKind::Path(QPath::TypeRelative(ty, segment)) + if let TyKind::Path(QPath::Resolved(None, p)) = &ty.kind + && let Res::SelfTyAlias { + is_trait_impl: true, .. + } = p.res + && let variant_ident = segment.ident + && let Some(variant_def) = adt_def.variants().iter().find(|v| v.ident(cx.tcx) == variant_ident) => + { + Some(variant_def) + }, + _ => None, + } +} + +fn check_enum<'tcx>( + cx: &LateContext<'tcx>, + item: &'tcx Item<'tcx>, + func_expr: &'tcx Expr<'tcx>, + adt_def: AdtDef<'tcx>, + is_const: bool, +) { + if let Some(variant_def) = extract_enum_variant(cx, func_expr, adt_def) && variant_def.fields.is_empty() && !variant_def.is_field_list_non_exhaustive() { @@ -158,11 +200,15 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex let variant_span = cx.tcx.def_span(variant_def.def_id); let indent_variant = indent_of(cx, variant_span).unwrap_or(0); + let Some(derive_snippet) = determine_derive_macro(cx, is_const) else { + return; + }; + let suggestions = vec![ (item.span, String::new()), // Remove the manual implementation ( enum_span.shrink_to_lo(), - format!("#[derive(Default)]\n{}", " ".repeat(indent_enum)), + format!("#[{derive_snippet}(Default)]\n{}", " ".repeat(indent_enum)), ), // Add the derive attribute ( variant_span.shrink_to_lo(), @@ -201,10 +247,20 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls { && !attrs.iter().any(|attr| attr.doc_str().is_some()) && cx.tcx.hir_attrs(impl_item_hir).is_empty() { + let is_const = of_trait.constness == hir::Constness::Const; if adt_def.is_struct() { - check_struct(cx, item, self_ty, func_expr, adt_def, args, cx.tcx.typeck_body(*b)); + check_struct( + cx, + item, + self_ty, + func_expr, + adt_def, + args, + cx.tcx.typeck_body(*b), + is_const, + ); } else if adt_def.is_enum() && self.msrv.meets(cx, msrvs::DEFAULT_ENUM_ATTRIBUTE) { - check_enum(cx, item, func_expr, adt_def); + check_enum(cx, item, func_expr, adt_def, is_const); } } } diff --git a/src/tools/clippy/clippy_lints/src/entry.rs b/src/tools/clippy/clippy_lints/src/entry.rs index 182cb4e46d2..a5ec6777b43 100644 --- a/src/tools/clippy/clippy_lints/src/entry.rs +++ b/src/tools/clippy/clippy_lints/src/entry.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_applicability, snippet_with_context}; use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_expr; @@ -194,7 +194,17 @@ impl<'tcx> LateLintPass<'tcx> for HashMapPass { if let Some(sugg) = sugg { span_lint_and_sugg(cx, MAP_ENTRY, expr.span, lint_msg, "try", sugg, app); } else { - span_lint(cx, MAP_ENTRY, expr.span, lint_msg); + span_lint_and_help( + cx, + MAP_ENTRY, + expr.span, + lint_msg, + None, + format!( + "consider using the `Entry` API: https://doc.rust-lang.org/std/collections/struct.{}.html#entry-api", + map_ty.name() + ), + ); } } } @@ -254,35 +264,28 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio _ => None, }); - match expr.kind { - ExprKind::MethodCall( - _, + if let ExprKind::MethodCall(_, map, [arg], _) = expr.kind + && let Expr { + kind: ExprKind::AddrOf(_, _, key), + span: key_span, + .. + } = arg + && key_span.eq_ctxt(expr.span) + { + let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; + let expr = ContainsExpr { + negated, map, - [ - Expr { - kind: ExprKind::AddrOf(_, _, key), - span: key_span, - .. - }, - ], - _, - ) if key_span.eq_ctxt(expr.span) => { - let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - let expr = ContainsExpr { - negated, - map, - key, - call_ctxt: expr.span.ctxt(), - }; - if cx.tcx.is_diagnostic_item(sym::btreemap_contains_key, id) { - Some((MapType::BTree, expr)) - } else if cx.tcx.is_diagnostic_item(sym::hashmap_contains_key, id) { - Some((MapType::Hash, expr)) - } else { - None - } - }, - _ => None, + key, + call_ctxt: expr.span.ctxt(), + }; + match cx.tcx.get_diagnostic_name(id) { + Some(sym::btreemap_contains_key) => Some((MapType::BTree, expr)), + Some(sym::hashmap_contains_key) => Some((MapType::Hash, expr)), + _ => None, + } + } else { + None } } @@ -311,7 +314,9 @@ struct InsertExpr<'tcx> { fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<InsertExpr<'tcx>> { if let ExprKind::MethodCall(_, map, [key, value], _) = expr.kind { let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?; - if cx.tcx.is_diagnostic_item(sym::btreemap_insert, id) || cx.tcx.is_diagnostic_item(sym::hashmap_insert, id) { + if let Some(insert) = cx.tcx.get_diagnostic_name(id) + && matches!(insert, sym::btreemap_insert | sym::hashmap_insert) + { Some(InsertExpr { map, key, value }) } else { None diff --git a/src/tools/clippy/clippy_lints/src/eta_reduction.rs b/src/tools/clippy/clippy_lints/src/eta_reduction.rs index 0eefc2f6109..2da1c2bad11 100644 --- a/src/tools/clippy/clippy_lints/src/eta_reduction.rs +++ b/src/tools/clippy/clippy_lints/src/eta_reduction.rs @@ -12,6 +12,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind, find_attr}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{ self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; @@ -148,10 +149,9 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx { return; } - let callee_ty_adjusted = typeck - .expr_adjustments(callee) - .last() - .map_or(callee_ty, |a| a.target.peel_refs()); + + let callee_ty_adjustments = typeck.expr_adjustments(callee); + let callee_ty_adjusted = callee_ty_adjustments.last().map_or(callee_ty, |a| a.target); let sig = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => { @@ -230,7 +230,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx }, _ => (), } + } else if let n_refs = + callee_ty_adjustments + .iter() + .rev() + .fold(0, |acc, adjustment| match adjustment.kind { + Adjust::Deref(Some(_)) => acc + 1, + Adjust::Deref(_) if acc > 0 => acc + 1, + _ => acc, + }) + && n_refs > 0 + { + snippet = format!("{}{snippet}", "*".repeat(n_refs)); } + let replace_with = match callee_ty_adjusted.kind() { ty::FnDef(def, _) => cx.tcx.def_descr(*def), _ => "function", diff --git a/src/tools/clippy/clippy_lints/src/float_literal.rs b/src/tools/clippy/clippy_lints/src/float_literal.rs index ccaf38aee4d..6178addfff1 100644 --- a/src/tools/clippy/clippy_lints/src/float_literal.rs +++ b/src/tools/clippy/clippy_lints/src/float_literal.rs @@ -1,11 +1,12 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::numeric_literal; +use clippy_utils::{ExprUseNode, expr_use_ctxt, numeric_literal}; use rustc_ast::ast::{LitFloatType, LitKind}; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, FloatTy}; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use std::fmt; declare_clippy_lint! { @@ -13,6 +14,8 @@ declare_clippy_lint! { /// Checks for float literals with a precision greater /// than that supported by the underlying type. /// + /// The lint is suppressed for literals with over `const_literal_digits_threshold` digits. + /// /// ### Why is this bad? /// Rust will truncate the literal silently. /// @@ -58,7 +61,21 @@ declare_clippy_lint! { "lossy whole number float literals" } -declare_lint_pass!(FloatLiteral => [EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL]); +pub struct FloatLiteral { + const_literal_digits_threshold: usize, +} + +impl_lint_pass!(FloatLiteral => [ + EXCESSIVE_PRECISION, LOSSY_FLOAT_LITERAL +]); + +impl FloatLiteral { + pub fn new(conf: &'static Conf) -> Self { + Self { + const_literal_digits_threshold: conf.const_literal_digits_threshold, + } + } +} impl<'tcx> LateLintPass<'tcx> for FloatLiteral { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { @@ -126,13 +143,25 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral { }, ); } - } else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) { + } else if digits > max as usize && count_digits(&float_str) < digits { + if digits >= self.const_literal_digits_threshold + && matches!(expr_use_ctxt(cx, expr).use_node(cx), ExprUseNode::ConstStatic(_)) + { + // If a big enough number of digits is specified and it's a constant + // we assume the user is definining a constant, and excessive precision is ok + return; + } span_lint_and_then( cx, EXCESSIVE_PRECISION, expr.span, "float has excessive precision", |diag| { + if digits >= self.const_literal_digits_threshold + && let Some(let_stmt) = maybe_let_stmt(cx, expr) + { + diag.span_note(let_stmt.span, "consider making it a `const` item"); + } diag.span_suggestion_verbose( expr.span, "consider changing the type or truncating it to", @@ -196,3 +225,11 @@ impl FloatFormat { } } } + +fn maybe_let_stmt<'a>(cx: &LateContext<'a>, expr: &hir::Expr<'_>) -> Option<&'a hir::LetStmt<'a>> { + let parent = cx.tcx.parent_hir_node(expr.hir_id); + match parent { + hir::Node::LetStmt(let_stmt) => Some(let_stmt), + _ => None, + } +} diff --git a/src/tools/clippy/clippy_lints/src/functions/mod.rs b/src/tools/clippy/clippy_lints/src/functions/mod.rs index ca5ea901814..5a40af42194 100644 --- a/src/tools/clippy/clippy_lints/src/functions/mod.rs +++ b/src/tools/clippy/clippy_lints/src/functions/mod.rs @@ -535,7 +535,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions { def_id: LocalDefId, ) { let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); - too_many_arguments::check_fn(cx, kind, decl, span, hir_id, self.too_many_arguments_threshold); + too_many_arguments::check_fn(cx, kind, decl, hir_id, def_id, self.too_many_arguments_threshold); too_many_lines::check_fn(cx, kind, body, span, def_id, self.too_many_lines_threshold); not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); diff --git a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs index 48d050aa36a..6c3c3d354ec 100644 --- a/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs +++ b/src/tools/clippy/clippy_lints/src/functions/too_many_arguments.rs @@ -1,5 +1,7 @@ use rustc_abi::ExternAbi; -use rustc_hir::{self as hir, intravisit}; +use rustc_hir as hir; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::intravisit::FnKind; use rustc_lint::LateContext; use rustc_span::Span; @@ -10,39 +12,18 @@ use super::TOO_MANY_ARGUMENTS; pub(super) fn check_fn( cx: &LateContext<'_>, - kind: intravisit::FnKind<'_>, + kind: FnKind<'_>, decl: &hir::FnDecl<'_>, - span: Span, hir_id: hir::HirId, + def_id: LocalDefId, too_many_arguments_threshold: u64, ) { // don't warn for implementations, it's not their fault - if !is_trait_impl_item(cx, hir_id) { + if !is_trait_impl_item(cx, hir_id) // don't lint extern functions decls, it's not their fault either - match kind { - intravisit::FnKind::Method( - _, - &hir::FnSig { - header: hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - .. - }, - ) - | intravisit::FnKind::ItemFn( - _, - _, - hir::FnHeader { - abi: ExternAbi::Rust, .. - }, - ) => check_arg_number( - cx, - decl, - span.with_hi(decl.output.span().hi()), - too_many_arguments_threshold, - ), - _ => {}, - } + && kind.header().is_some_and(|header| header.abi == ExternAbi::Rust) + { + check_arg_number(cx, decl, cx.tcx.def_span(def_id), too_many_arguments_threshold); } } diff --git a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs index 7158f9419c1..b50d91f1014 100644 --- a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs +++ b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs @@ -2,16 +2,16 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_with_context; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, is_res_lang_ctor, - path_res, peel_blocks, + path_res, peel_blocks, sym, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; declare_clippy_lint! { @@ -71,21 +71,21 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind - && let ctxt = expr.span.ctxt() - && then_expr.span.ctxt() == ctxt + && !expr.span.from_expansion() + && !then_expr.span.from_expansion() && is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome) && is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) - && !expr.span.in_external_macro(cx.sess().source_map()) && self.msrv.meets(cx, msrvs::BOOL_THEN) && !contains_return(then_block.stmts) { let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(cx, msrvs::BOOL_THEN_SOME) { - "then_some" + sym::then_some } else { - "then" + sym::then }; + let ctxt = expr.span.ctxt(); span_lint_and_then( cx, @@ -98,16 +98,18 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { } let mut app = Applicability::MachineApplicable; - let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app) + let cond_snip = Sugg::hir_with_context(cx, cond, ctxt, "[condition]", &mut app) .maybe_paren() .to_string(); let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0; - let method_body = if let Some(first_stmt) = then_block.stmts.first() { - let (block_snippet, _) = - snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app); - let closure = if method_name == "then" { "|| " } else { "" }; - format!("{closure} {{ {block_snippet}; {arg_snip} }}") - } else if method_name == "then" { + let method_body = if let Some(first_stmt) = then_block.stmts.first() + && let Some(first_stmt_span) = walk_span_to_context(first_stmt.span, ctxt) + { + let block_snippet = + snippet_with_applicability(cx, first_stmt_span.until(then_expr.span), "..", &mut app); + let closure = if method_name == sym::then { "|| " } else { "" }; + format!("{closure} {{ {} {arg_snip} }}", block_snippet.trim_end()) + } else if method_name == sym::then { (std::borrow::Cow::Borrowed("|| ") + arg_snip).into_owned() } else { arg_snip.into_owned() diff --git a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs index 91f65d0b79c..13117f60abd 100644 --- a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs +++ b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_context; use clippy_utils::sugg::Sugg; -use clippy_utils::ty; +use clippy_utils::{is_path_diagnostic_item, ty}; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -107,8 +107,7 @@ impl LateLintPass<'_> for InstantSubtraction { fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { if let ExprKind::Call(fn_expr, []) = expr_block.kind - && let Some(fn_id) = clippy_utils::path_def_id(cx, fn_expr) - && cx.tcx.is_diagnostic_item(sym::instant_now, fn_id) + && is_path_diagnostic_item(cx, fn_expr, sym::instant_now) { true } else { diff --git a/src/tools/clippy/clippy_lints/src/large_include_file.rs b/src/tools/clippy/clippy_lints/src/large_include_file.rs index 8707612fbdd..48ce1afc6e6 100644 --- a/src/tools/clippy/clippy_lints/src/large_include_file.rs +++ b/src/tools/clippy/clippy_lints/src/large_include_file.rs @@ -64,8 +64,8 @@ impl LateLintPass<'_> for LargeIncludeFile { } && len as u64 > self.max_file_size && let Some(macro_call) = root_macro_call_first_node(cx, expr) - && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) - || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) + && let Some(macro_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) + && matches!(macro_name, sym::include_bytes_macro | sym::include_str_macro) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs index 57deb011f2b..f44a5fdf715 100644 --- a/src/tools/clippy/clippy_lints/src/len_zero.rs +++ b/src/tools/clippy/clippy_lints/src/len_zero.rs @@ -355,12 +355,15 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le return Some(LenOutput::Integral); } - if let Res::Def(_, def_id) = res { - if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_first_generic_integral(segment) { - return Some(LenOutput::Option(def_id)); - } else if cx.tcx.is_diagnostic_item(sym::Result, def_id) && is_first_generic_integral(segment) { - return Some(LenOutput::Result(def_id)); + if let Res::Def(_, def_id) = res + && let Some(res) = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::Option) => Some(LenOutput::Option(def_id)), + Some(sym::Result) => Some(LenOutput::Result(def_id)), + _ => None, } + && is_first_generic_integral(segment) + { + return Some(res); } return None; @@ -368,11 +371,10 @@ fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<Le match *sig.output().kind() { ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral), - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did())) - }, - ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => { - subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did())) + ty::Adt(adt, subs) if subs.type_at(0).is_integral() => match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => Some(LenOutput::Option(adt.did())), + Some(sym::Result) => Some(LenOutput::Result(adt.did())), + _ => None, }, _ => None, } diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs index c8594cf35e2..a89cf3fdc1e 100644 --- a/src/tools/clippy/clippy_lints/src/lib.rs +++ b/src/tools/clippy/clippy_lints/src/lib.rs @@ -84,6 +84,7 @@ mod attrs; mod await_holding_invalid; mod blocks_in_conditions; mod bool_assert_comparison; +mod bool_comparison; mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; @@ -474,10 +475,10 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_late_pass(move |_| Box::new(types::Types::new(conf))); store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf))); store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant)); - store.register_late_pass(|_| Box::new(float_literal::FloatLiteral)); + store.register_late_pass(move |_| Box::new(float_literal::FloatLiteral::new(conf))); store.register_late_pass(|_| Box::new(ptr::Ptr)); store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool)); - store.register_late_pass(|_| Box::new(needless_bool::BoolComparison)); + store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison)); store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); store.register_late_pass(|_| Box::new(misc::LintPass)); store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); @@ -655,7 +656,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); store.register_late_pass(|_| Box::<macro_use::MacroUseImports>::default()); store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); + store.register_late_pass(|_| Box::<unwrap_in_result::UnwrapInResult>::default()); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); let attrs = attr_storage.clone(); diff --git a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs index a71e6963f8c..74c0b178018 100644 --- a/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/infinite_loop.rs @@ -4,7 +4,8 @@ use hir::intravisit::{Visitor, walk_expr}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir::{ - self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind, + self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnRetTy, + FnSig, Node, TyKind, }; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -73,7 +74,11 @@ fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> boo if let Node::Expr(Expr { kind: ExprKind::Closure(Closure { - kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), + kind: + ClosureKind::Coroutine(CoroutineKind::Desugared( + CoroutineDesugaring::Async, + CoroutineSource::Block | CoroutineSource::Closure, + )), .. }), .. diff --git a/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs index a9944d64ce2..8a2d0036203 100644 --- a/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/missing_spin_loop.rs @@ -1,10 +1,10 @@ use super::MISSING_SPIN_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::std_or_core; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; fn unpack_cond<'tcx>(cond: &'tcx Expr<'tcx>) -> &'tcx Expr<'tcx> { @@ -39,8 +39,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' ) = body.kind && let ExprKind::MethodCall(method, callee, ..) = unpack_cond(cond).kind && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name) - && let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind() - && cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()) + && let callee_ty = cx.typeck_results().expr_ty(callee) + && is_type_diagnostic_item(cx, callee_ty, sym::AtomicBool) && let Some(std_or_core) = std_or_core(cx) { span_lint_and_sugg( diff --git a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs index 7bb684d65bb..11edb929d70 100644 --- a/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/needless_range_loop.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::has_iter_method; use clippy_utils::visitors::is_local_used; -use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, sugg}; +use clippy_utils::{SpanlessEq, contains_name, higher, is_integer_const, peel_hir_expr_while, sugg}; use rustc_ast::ast; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::Applicability; @@ -253,12 +253,38 @@ struct VarVisitor<'a, 'tcx> { impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { - let index_used_directly = matches!(idx.kind, ExprKind::Path(_)); + let mut used_cnt = 0; + // It is `true` if all indices are direct + let mut index_used_directly = true; + + // Handle initial index + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + // Handle nested indices + let seqexpr = peel_hir_expr_while(seqexpr, |e| { + if let ExprKind::Index(e, idx, _) = e.kind { + if is_local_used(self.cx, idx, self.var) { + used_cnt += 1; + index_used_directly &= matches!(idx.kind, ExprKind::Path(_)); + } + Some(e) + } else { + None + } + }); + + match used_cnt { + 0 => return true, + n if n > 1 => self.nonindex = true, // Optimize code like `a[i][i]` + _ => {}, + } + if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name && let QPath::Resolved(None, seqvar) = *seqpath && seqvar.segments.len() == 1 - && is_local_used(self.cx, idx, self.var) { if self.prefer_mutable { self.indexed_mut.insert(seqvar.segments[0].ident.name); @@ -312,7 +338,6 @@ impl<'tcx> VarVisitor<'_, 'tcx> { impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind - // a range index op && let Some(trait_id) = self .cx .typeck_results() diff --git a/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs b/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs index 51e21aa9734..13b93d2c009 100644 --- a/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/src/tools/clippy/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,12 +1,12 @@ use super::UNUSED_ENUMERATE_INDEX; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{pat_is_wild, sugg}; use rustc_errors::Applicability; use rustc_hir::def::DefKind; use rustc_hir::{Expr, ExprKind, Pat, PatKind}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::sym; /// Checks for the `UNUSED_ENUMERATE_INDEX` lint. @@ -17,8 +17,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_ && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind && let ty = cx.typeck_results().expr_ty(arg) && pat_is_wild(cx, &index.kind, body) - && let ty::Adt(base, _) = *ty.kind() - && cx.tcx.is_diagnostic_item(sym::Enumerate, base.did()) + && is_type_diagnostic_item(cx, ty, sym::Enumerate) && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) { diff --git a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs index ac8c88f0205..2eebb2430fd 100644 --- a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs +++ b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs @@ -97,11 +97,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { return; } - if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) { - if let ExprKind::Match(recv, [arm, ..], _) = expr.kind { - let range = check_pat(&arm.pat.kind); - check_is_ascii(cx, macro_call.span, recv, &range, None); - } + let (arg, span, range) = if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) + && let ExprKind::Match(recv, [arm, ..], _) = expr.kind + { + let recv = peel_ref_operators(cx, recv); + let range = check_pat(&arm.pat.kind); + (recv, macro_call.span, range) } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind && path.ident.name == sym::contains && let Some(higher::Range { @@ -112,10 +113,14 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { && !matches!(cx.typeck_results().expr_ty(arg).peel_refs().kind(), ty::Param(_)) { let arg = peel_ref_operators(cx, arg); - let ty_sugg = get_ty_sugg(cx, arg); let range = check_expr_range(start, end); - check_is_ascii(cx, expr.span, arg, &range, ty_sugg); - } + (arg, expr.span, range) + } else { + return; + }; + + let ty_sugg = get_ty_sugg(cx, arg); + check_is_ascii(cx, span, arg, &range, ty_sugg); } } @@ -146,9 +151,8 @@ fn check_is_ascii( CharRange::HexDigit => "is_ascii_hexdigit", CharRange::Otherwise | CharRange::LowerHexLetter | CharRange::UpperHexLetter => return, }; - let default_snip = ".."; let mut app = Applicability::MachineApplicable; - let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_paren(); + let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), "_", &mut app).maybe_paren(); let mut suggestion = vec![(span, format!("{recv}.{sugg}()"))]; if let Some((ty_span, ty)) = ty_sugg { suggestion.push((ty_span, format!("{recv}: {ty}"))); @@ -182,7 +186,7 @@ fn check_pat(pat_kind: &PatKind<'_>) -> CharRange { CharRange::Otherwise } }, - PatKind::Range(Some(start), Some(end), kind) if *kind == RangeEnd::Included => check_range(start, end), + PatKind::Range(Some(start), Some(end), RangeEnd::Included) => check_range(start, end), _ => CharRange::Otherwise, } } diff --git a/src/tools/clippy/clippy_lints/src/manual_retain.rs b/src/tools/clippy/clippy_lints/src/manual_retain.rs index 98e8b1f5cf9..7fb88763e64 100644 --- a/src/tools/clippy/clippy_lints/src/manual_retain.rs +++ b/src/tools/clippy/clippy_lints/src/manual_retain.rs @@ -123,8 +123,8 @@ fn check_iter( ) { if let hir::ExprKind::MethodCall(_, filter_expr, [], _) = &target_expr.kind && let Some(copied_def_id) = cx.typeck_results().type_dependent_def_id(target_expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::iter_copied, copied_def_id) - || cx.tcx.is_diagnostic_item(sym::iter_cloned, copied_def_id)) + && let Some(copied_name) = cx.tcx.get_diagnostic_name(copied_def_id) + && matches!(copied_name, sym::iter_copied | sym::iter_cloned) && let hir::ExprKind::MethodCall(_, iter_expr, [_], _) = &filter_expr.kind && let Some(filter_def_id) = cx.typeck_results().type_dependent_def_id(filter_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::iter_filter, filter_def_id) @@ -243,9 +243,9 @@ fn make_sugg( } fn match_acceptable_sym(cx: &LateContext<'_>, collect_def_id: DefId) -> bool { - ACCEPTABLE_METHODS - .iter() - .any(|&method| cx.tcx.is_diagnostic_item(method, collect_def_id)) + cx.tcx + .get_diagnostic_name(collect_def_id) + .is_some_and(|collect_name| ACCEPTABLE_METHODS.contains(&collect_name)) } fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, msrv: Msrv) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/manual_strip.rs b/src/tools/clippy/clippy_lints/src/manual_strip.rs index 6bf43a1c6d4..07cce4046ca 100644 --- a/src/tools/clippy/clippy_lints/src/manual_strip.rs +++ b/src/tools/clippy/clippy_lints/src/manual_strip.rs @@ -75,12 +75,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { && let ExprKind::Path(target_path) = &target_arg.kind && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) { - let strip_kind = if cx.tcx.is_diagnostic_item(sym::str_starts_with, method_def_id) { - StripKind::Prefix - } else if cx.tcx.is_diagnostic_item(sym::str_ends_with, method_def_id) { - StripKind::Suffix - } else { - return; + let strip_kind = match cx.tcx.get_diagnostic_name(method_def_id) { + Some(sym::str_starts_with) => StripKind::Prefix, + Some(sym::str_ends_with) => StripKind::Suffix, + _ => return, }; let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err { diff --git a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs index af6a1b07a49..39e5289c62a 100644 --- a/src/tools/clippy/clippy_lints/src/map_unit_fn.rs +++ b/src/tools/clippy/clippy_lints/src/map_unit_fn.rs @@ -116,8 +116,10 @@ fn is_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { /// The expression inside a closure may or may not have surrounding braces and /// semicolons, which causes problems when generating a suggestion. Given an /// expression that evaluates to '()' or '!', recursively remove useless braces -/// and semi-colons until is suitable for including in the suggestion template -fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Span> { +/// and semi-colons until is suitable for including in the suggestion template. +/// The `bool` is `true` when the resulting `span` needs to be enclosed in an +/// `unsafe` block. +fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<(Span, bool)> { if !is_unit_expression(cx, expr) { return None; } @@ -125,22 +127,24 @@ fn reduce_unit_expression(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option< match expr.kind { hir::ExprKind::Call(_, _) | hir::ExprKind::MethodCall(..) => { // Calls can't be reduced any more - Some(expr.span) + Some((expr.span, false)) }, hir::ExprKind::Block(block, _) => { + let is_unsafe = matches!(block.rules, hir::BlockCheckMode::UnsafeBlock(_)); match (block.stmts, block.expr.as_ref()) { ([], Some(inner_expr)) => { // If block only contains an expression, // reduce `{ X }` to `X` reduce_unit_expression(cx, inner_expr) + .map(|(span, inner_is_unsafe)| (span, inner_is_unsafe || is_unsafe)) }, ([inner_stmt], None) => { // If block only contains statements, // reduce `{ X; }` to `X` or `X;` match inner_stmt.kind { - hir::StmtKind::Let(local) => Some(local.span), - hir::StmtKind::Expr(e) => Some(e.span), - hir::StmtKind::Semi(..) => Some(inner_stmt.span), + hir::StmtKind::Let(local) => Some((local.span, is_unsafe)), + hir::StmtKind::Expr(e) => Some((e.span, is_unsafe)), + hir::StmtKind::Semi(..) => Some((inner_stmt.span, is_unsafe)), hir::StmtKind::Item(..) => None, } }, @@ -228,10 +232,11 @@ fn lint_map_unit_fn( let msg = suggestion_msg("closure", map_type); span_lint_and_then(cx, lint, expr.span, msg, |diag| { - if let Some(reduced_expr_span) = reduce_unit_expression(cx, closure_expr) { + if let Some((reduced_expr_span, is_unsafe)) = reduce_unit_expression(cx, closure_expr) { let mut applicability = Applicability::MachineApplicable; + let (prefix_is_unsafe, suffix_is_unsafe) = if is_unsafe { ("unsafe { ", " }") } else { ("", "") }; let suggestion = format!( - "if let {0}({1}) = {2} {{ {3} }}", + "if let {0}({1}) = {2} {{ {prefix_is_unsafe}{3}{suffix_is_unsafe} }}", variant, snippet_with_applicability(cx, binding.pat.span, "_", &mut applicability), snippet_with_applicability(cx, var_arg.span, "_", &mut applicability), diff --git a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs index 5b50efad3e4..aaf559fc443 100644 --- a/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs +++ b/src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs @@ -4,20 +4,22 @@ use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet; use clippy_utils::visitors::is_local_used; use clippy_utils::{ - SpanlessEq, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, peel_ref_operators, + SpanlessEq, get_ref_operators, is_res_lang_ctor, is_unit_expr, path_to_local, peel_blocks_with_stmt, + peel_ref_operators, }; +use rustc_ast::BorrowKind; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; use super::{COLLAPSIBLE_MATCH, pat_contains_disallowed_or}; -pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>], msrv: Msrv) { +pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>], msrv: Msrv) { if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) { for arm in arms { - check_arm(cx, true, arm.pat, arm.body, arm.guard, Some(els_arm.body), msrv); + check_arm(cx, true, arm.pat, expr, arm.body, arm.guard, Some(els_arm.body), msrv); } } } @@ -27,15 +29,18 @@ pub(super) fn check_if_let<'tcx>( pat: &'tcx Pat<'_>, body: &'tcx Expr<'_>, else_expr: Option<&'tcx Expr<'_>>, + let_expr: &'tcx Expr<'_>, msrv: Msrv, ) { - check_arm(cx, false, pat, body, None, else_expr, msrv); + check_arm(cx, false, pat, let_expr, body, None, else_expr, msrv); } +#[allow(clippy::too_many_arguments)] fn check_arm<'tcx>( cx: &LateContext<'tcx>, outer_is_match: bool, outer_pat: &'tcx Pat<'tcx>, + outer_cond: &'tcx Expr<'tcx>, outer_then_body: &'tcx Expr<'tcx>, outer_guard: Option<&'tcx Expr<'tcx>>, outer_else_body: Option<&'tcx Expr<'tcx>>, @@ -82,6 +87,9 @@ fn check_arm<'tcx>( }, IfLetOrMatch::Match(_, arms, ..) => !arms.iter().any(|arm| is_local_used(cx, arm, binding_id)), } + // Check if the inner expression contains any borrows/dereferences + && let ref_types = get_ref_operators(cx, inner_scrutinee) + && let Some(method) = build_ref_method_chain(ref_types) { let msg = format!( "this `{}` can be collapsed into the outer `{}`", @@ -103,6 +111,10 @@ fn check_arm<'tcx>( let mut help_span = MultiSpan::from_spans(vec![binding_span, inner_then_pat.span]); help_span.push_span_label(binding_span, "replace this binding"); help_span.push_span_label(inner_then_pat.span, format!("with this pattern{replace_msg}")); + if !method.is_empty() { + let outer_cond_msg = format!("use: `{}{}`", snippet(cx, outer_cond.span, ".."), method); + help_span.push_span_label(outer_cond.span, outer_cond_msg); + } diag.span_help( help_span, "the outer pattern can be modified to include the inner pattern", @@ -148,3 +160,30 @@ fn find_pat_binding_and_is_innermost_parent_pat_struct(pat: &Pat<'_>, hir_id: Hi }); (span, is_innermost_parent_pat_struct) } + +/// Builds a chain of reference-manipulation method calls (e.g., `.as_ref()`, `.as_mut()`, +/// `.copied()`) based on reference operators +fn build_ref_method_chain(expr: Vec<&Expr<'_>>) -> Option<String> { + let mut req_method_calls = String::new(); + + for ref_operator in expr { + match ref_operator.kind { + ExprKind::AddrOf(BorrowKind::Raw, _, _) => { + return None; + }, + ExprKind::AddrOf(_, m, _) if m.is_mut() => { + req_method_calls.push_str(".as_mut()"); + }, + ExprKind::AddrOf(_, _, _) => { + req_method_calls.push_str(".as_ref()"); + }, + // Deref operator is the only operator that this function should have received + ExprKind::Unary(_, _) => { + req_method_calls.push_str(".copied()"); + }, + _ => (), + } + } + + Some(req_method_calls) +} diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs index c128fc40b73..6f49c552411 100644 --- a/src/tools/clippy/clippy_lints/src/matches/mod.rs +++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs @@ -1073,7 +1073,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source); } - collapsible_match::check_match(cx, arms, self.msrv); + collapsible_match::check_match(cx, ex, arms, self.msrv); if !from_expansion { // These don't depend on a relationship between multiple arms match_wild_err_arm::check(cx, ex, arms); @@ -1137,7 +1137,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches { match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr); } } else if let Some(if_let) = higher::IfLet::hir(cx, expr) { - collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else, self.msrv); + collapsible_match::check_if_let( + cx, + if_let.let_pat, + if_let.if_then, + if_let.if_else, + if_let.let_expr, + self.msrv, + ); significant_drop_in_scrutinee::check_if_let(cx, expr, if_let.let_expr, if_let.if_then, if_let.if_else); if !from_expansion { if let Some(else_expr) = if_let.if_else { diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 027dd7ce053..81fecc87256 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -226,11 +226,12 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] enum SigDropHolder { /// No values with significant drop present in this expression. /// /// Expressions that we've emitted lints do not count. + #[default] None, /// Some field in this expression references to values with significant drop. /// @@ -244,12 +245,6 @@ enum SigDropHolder { Moved, } -impl Default for SigDropHolder { - fn default() -> Self { - Self::None - } -} - struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, parent_expr: Option<&'tcx Expr<'tcx>>, diff --git a/src/tools/clippy/clippy_lints/src/matches/try_err.rs b/src/tools/clippy/clippy_lints/src/matches/try_err.rs index ff7769af1df..af90cb5e673 100644 --- a/src/tools/clippy/clippy_lints/src/matches/try_err.rs +++ b/src/tools/clippy/clippy_lints/src/matches/try_err.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::option_arg_ty; use clippy_utils::{get_parent_expr, is_res_lang_ctor, path_res}; use rustc_errors::Applicability; use rustc_hir::LangItem::ResultErr; @@ -28,25 +28,15 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine && is_res_lang_ctor(cx, path_res(cx, err_fun), ResultErr) && let Some(return_ty) = find_return_type(cx, &expr.kind) { - let prefix; - let suffix; - let err_ty; - - if let Some(ty) = result_error_type(cx, return_ty) { - prefix = "Err("; - suffix = ")"; - err_ty = ty; + let (prefix, suffix, err_ty) = if let Some(ty) = result_error_type(cx, return_ty) { + ("Err(", ")", ty) } else if let Some(ty) = poll_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Err("; - suffix = "))"; - err_ty = ty; + ("Poll::Ready(Err(", "))", ty) } else if let Some(ty) = poll_option_result_error_type(cx, return_ty) { - prefix = "Poll::Ready(Some(Err("; - suffix = ")))"; - err_ty = ty; + ("Poll::Ready(Some(Err(", ")))", ty) } else { return; - } + }; span_lint_and_then( cx, @@ -88,8 +78,8 @@ fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> O /// Extracts the error type from Result<T, E>. fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { - if let ty::Adt(_, subst) = ty.kind() - && is_type_diagnostic_item(cx, ty, sym::Result) + if let ty::Adt(def, subst) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::Result, def.did()) { Some(subst.type_at(1)) } else { @@ -101,11 +91,9 @@ fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'t fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> { if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) - && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, ready_def.did()) { - Some(ready_subst.type_at(1)) + let ready_ty = subst.type_at(0); + result_error_type(cx, ready_ty) } else { None } @@ -116,13 +104,9 @@ fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> if let ty::Adt(def, subst) = ty.kind() && cx.tcx.lang_items().get(LangItem::Poll) == Some(def.did()) && let ready_ty = subst.type_at(0) - && let ty::Adt(ready_def, ready_subst) = ready_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Option, ready_def.did()) - && let some_ty = ready_subst.type_at(0) - && let ty::Adt(some_def, some_subst) = some_ty.kind() - && cx.tcx.is_diagnostic_item(sym::Result, some_def.did()) + && let Some(some_ty) = option_arg_ty(cx, ready_ty) { - Some(some_subst.type_at(1)) + result_error_type(cx, some_ty) } else { None } diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs index 28efd2038b3..e39916f733d 100644 --- a/src/tools/clippy/clippy_lints/src/mem_replace.rs +++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs @@ -215,7 +215,8 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' && let ExprKind::Path(ref repl_func_qpath) = repl_func.kind && let Some(repl_def_id) = cx.qpath_res(repl_func_qpath, repl_func.hir_id).opt_def_id() { - if cx.tcx.is_diagnostic_item(sym::mem_uninitialized, repl_def_id) { + let repl_name = cx.tcx.get_diagnostic_name(repl_def_id); + if repl_name == Some(sym::mem_uninitialized) { let Some(top_crate) = std_or_core(cx) else { return }; let mut applicability = Applicability::MachineApplicable; span_lint_and_sugg( @@ -230,9 +231,7 @@ fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<' ), applicability, ); - } else if cx.tcx.is_diagnostic_item(sym::mem_zeroed, repl_def_id) - && !cx.typeck_results().expr_ty(src).is_primitive() - { + } else if repl_name == Some(sym::mem_zeroed) && !cx.typeck_results().expr_ty(src).is_primitive() { span_lint_and_help( cx, MEM_REPLACE_WITH_UNINIT, diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs index 96e2de0dc1c..65583c6a981 100644 --- a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs +++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs @@ -24,9 +24,10 @@ pub(super) fn check( && let Some(name) = cx.tcx.get_diagnostic_name(adt.did()) { let caller_type = match name { - sym::Rc => "Rc", - sym::Arc => "Arc", - sym::RcWeak | sym::ArcWeak => "Weak", + sym::Rc => "std::rc::Rc", + sym::Arc => "std::sync::Arc", + sym::RcWeak => "std::rc::Weak", + sym::ArcWeak => "std::sync::Weak", _ => return, }; span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs index 5b8457bdd16..2da0f8341b1 100644 --- a/src/tools/clippy/clippy_lints/src/methods/filter_map.rs +++ b/src/tools/clippy/clippy_lints/src/methods/filter_map.rs @@ -233,18 +233,16 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym::is_some { - Some(Self::IsSome { + match (cx.tcx.get_diagnostic_name(recv_ty.did()), path.ident.name) { + (Some(sym::Option), sym::is_some) => Some(Self::IsSome { receiver, side_effect_expr_span, - }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym::is_ok { - Some(Self::IsOk { + }), + (Some(sym::Result), sym::is_ok) => Some(Self::IsOk { receiver, side_effect_expr_span, - }) - } else { - None + }), + _ => None, } } else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some() // we know for a fact that the wildcard pattern is the second arm diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs index 9a62b719a8f..fa8f9d640ee 100644 --- a/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/src/tools/clippy/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -24,32 +24,29 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> let ty::Adt(adt, substs) = cx.typeck_results().expr_ty(iter).kind() else { return None; }; - let did = adt.did(); - if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { - // For array::IntoIter<T, const N: usize>, the length is the second generic - // parameter. - substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) - } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) - && let ExprKind::MethodCall(_, recv, ..) = iter.kind - { - if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { - // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) - len.try_to_target_usize(cx.tcx).map(u128::from) - } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { - match args { - VecArgs::Vec(vec) => vec.len().try_into().ok(), - VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::ArrayIntoIter) => { + // For array::IntoIter<T, const N: usize>, the length is the second generic + // parameter. + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) + }, + Some(sym::SliceIter) if let ExprKind::MethodCall(_, recv, ..) = iter.kind => { + if let ty::Array(_, len) = cx.typeck_results().expr_ty(recv).peel_refs().kind() { + // For slice::Iter<'_, T>, the receiver might be an array literal: [1,2,3].iter().skip(..) + len.try_to_target_usize(cx.tcx).map(u128::from) + } else if let Some(args) = VecArgs::hir(cx, expr_or_init(cx, recv)) { + match args { + VecArgs::Vec(vec) => vec.len().try_into().ok(), + VecArgs::Repeat(_, len) => expr_as_u128(cx, len), + } + } else { + None } - } else { - None - } - } else if cx.tcx.is_diagnostic_item(sym::IterEmpty, did) { - Some(0) - } else if cx.tcx.is_diagnostic_item(sym::IterOnce, did) { - Some(1) - } else { - None + }, + Some(sym::IterEmpty) => Some(0), + Some(sym::IterOnce) => Some(1), + _ => None, } } diff --git a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs index f7bb8c1d696..750f933330a 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_flatten.rs @@ -50,10 +50,10 @@ fn try_get_caller_ty_name_and_method_name( } } else { if let ty::Adt(adt, _) = cx.typeck_results().expr_ty(caller_expr).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) { - return Some(("Option", "and_then")); - } else if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) { - return Some(("Result", "and_then")); + match cx.tcx.get_diagnostic_name(adt.did()) { + Some(sym::Option) => return Some(("Option", "and_then")), + Some(sym::Result) => return Some(("Result", "and_then")), + _ => {}, } } None diff --git a/src/tools/clippy/clippy_lints/src/methods/map_identity.rs b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs index 98def66ca14..a98cfff8bfb 100644 --- a/src/tools/clippy/clippy_lints/src/methods/map_identity.rs +++ b/src/tools/clippy/clippy_lints/src/methods/map_identity.rs @@ -1,14 +1,16 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{is_expr_untyped_identity_function, is_trait_method, path_to_local}; -use rustc_ast::BindingMode; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{is_copy, is_type_diagnostic_item}; +use clippy_utils::{is_expr_untyped_identity_function, is_mutable, is_trait_method, path_to_local_with_projections}; use rustc_errors::Applicability; -use rustc_hir::{self as hir, Node, PatKind}; -use rustc_lint::LateContext; +use rustc_hir::{self as hir, ExprKind, Node, PatKind}; +use rustc_lint::{LateContext, LintContext}; use rustc_span::{Span, Symbol, sym}; use super::MAP_IDENTITY; +const MSG: &str = "unnecessary map of the identity function"; + pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -23,26 +25,70 @@ pub(super) fn check( || is_type_diagnostic_item(cx, caller_ty, sym::Result) || is_type_diagnostic_item(cx, caller_ty, sym::Option)) && is_expr_untyped_identity_function(cx, map_arg) - && let Some(sugg_span) = expr.span.trim_start(caller.span) + && let Some(call_span) = expr.span.trim_start(caller.span) { - // If the result of `.map(identity)` is used as a mutable reference, - // the caller must not be an immutable binding. - if cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr() - && let Some(hir_id) = path_to_local(caller) - && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) - && !matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..)) - { - return; - } + let main_sugg = (call_span, String::new()); + let mut app = if is_copy(cx, caller_ty) { + // there is technically a behavioral change here for `Copy` iterators, where + // `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and + // changing it to `iter.next()` mutates iter directly + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; + + let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr(); + if needs_to_be_mutable && !is_mutable(cx, caller) { + if let Some(hir_id) = path_to_local_with_projections(caller) + && let Node::Pat(pat) = cx.tcx.hir_node(hir_id) + && let PatKind::Binding(_, _, ident, _) = pat.kind + { + // We can reach the binding -- suggest making it mutable + let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))]; + + let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app); - span_lint_and_sugg( - cx, - MAP_IDENTITY, - sugg_span, - "unnecessary map of the identity function", - format!("remove the call to `{name}`"), - String::new(), - Applicability::MachineApplicable, - ); + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.multipart_suggestion( + format!("remove the call to `{name}`, and make `{ident}` mutable"), + suggs, + app, + ); + }); + } else { + // If we can't make the binding mutable, prevent the suggestion from being automatically applied, + // and add a complementary help message. + app = Applicability::Unspecified; + + let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id) + && let ExprKind::MethodCall(method, ..) = expr.kind + { + Some(method.ident) + } else { + None + }; + + span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| { + diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app); + + let note = if let Some(method_requiring_mut) = method_requiring_mut { + format!("this must be made mutable to use `{method_requiring_mut}`") + } else { + "this must be made mutable".to_string() + }; + diag.span_note(caller.span, note); + }); + } + } else { + span_lint_and_sugg( + cx, + MAP_IDENTITY, + main_sugg.0, + MSG, + format!("remove the call to `{name}`"), + main_sugg.1, + app, + ); + } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs index 63ee922acfa..906ead16fd0 100644 --- a/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs +++ b/src/tools/clippy/clippy_lints/src/methods/option_as_ref_deref.rs @@ -38,17 +38,13 @@ pub(super) fn check( ]; let is_deref = match map_arg.kind { - hir::ExprKind::Path(ref expr_qpath) => { - cx.qpath_res(expr_qpath, map_arg.hir_id) - .opt_def_id() - .is_some_and(|fun_def_id| { - cx.tcx.is_diagnostic_item(sym::deref_method, fun_def_id) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, fun_def_id) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, fun_def_id)) - }) - }, + hir::ExprKind::Path(ref expr_qpath) => cx + .qpath_res(expr_qpath, map_arg.hir_id) + .opt_def_id() + .and_then(|fun_def_id| cx.tcx.get_diagnostic_name(fun_def_id)) + .is_some_and(|fun_name| { + matches!(fun_name, sym::deref_method | sym::deref_mut_method) || deref_aliases.contains(&fun_name) + }), hir::ExprKind::Closure(&hir::Closure { body, .. }) => { let closure_body = cx.tcx.hir_body(body); let closure_expr = peel_blocks(closure_body.value); @@ -63,13 +59,11 @@ pub(super) fn check( .map(|x| &x.kind) .collect::<Box<[_]>>() && let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj + && let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap() + && let Some(method_name) = cx.tcx.get_diagnostic_name(method_did) { - let method_did = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id).unwrap(); - cx.tcx.is_diagnostic_item(sym::deref_method, method_did) - || cx.tcx.is_diagnostic_item(sym::deref_mut_method, method_did) - || deref_aliases - .iter() - .any(|&sym| cx.tcx.is_diagnostic_item(sym, method_did)) + matches!(method_name, sym::deref_method | sym::deref_mut_method) + || deref_aliases.contains(&method_name) } else { false } diff --git a/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs index 3e64e15dc86..1a760ea733d 100644 --- a/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs +++ b/src/tools/clippy/clippy_lints/src/methods/or_then_unwrap.rs @@ -62,7 +62,7 @@ fn get_content_if_ctor_matches(cx: &LateContext<'_>, expr: &Expr<'_>, item: Lang if let ExprKind::Call(some_expr, [arg]) = expr.kind && is_res_lang_ctor(cx, path_res(cx, some_expr), item) { - Some(arg.span) + Some(arg.span.source_callsite()) } else { None } diff --git a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs index 407f2e80aff..6738bbf0a12 100644 --- a/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs +++ b/src/tools/clippy/clippy_lints/src/methods/read_line_without_trim.rs @@ -31,8 +31,8 @@ fn parse_fails_on_trailing_newline(ty: Ty<'_>) -> bool { } pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) { - if let Some(recv_adt) = cx.typeck_results().expr_ty(recv).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Stdin, recv_adt.did()) + let recv_ty = cx.typeck_results().expr_ty(recv); + if is_type_diagnostic_item(cx, recv_ty, sym::Stdin) && let ExprKind::Path(QPath::Resolved(_, path)) = arg.peel_borrows().kind && let Res::Local(local_id) = path.res { diff --git a/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs index ccdf5529d53..ef3d7acdc01 100644 --- a/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs +++ b/src/tools/clippy/clippy_lints/src/methods/single_char_add_str.rs @@ -5,10 +5,10 @@ use rustc_span::sym; pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, receiver: &hir::Expr<'_>, args: &[hir::Expr<'_>]) { if let Some(fn_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { - if cx.tcx.is_diagnostic_item(sym::string_push_str, fn_def_id) { - single_char_push_string::check(cx, expr, receiver, args); - } else if cx.tcx.is_diagnostic_item(sym::string_insert_str, fn_def_id) { - single_char_insert_string::check(cx, expr, receiver, args); + match cx.tcx.get_diagnostic_name(fn_def_id) { + Some(sym::string_push_str) => single_char_push_string::check(cx, expr, receiver, args), + Some(sym::string_insert_str) => single_char_insert_string::check(cx, expr, receiver, args), + _ => {}, } } } diff --git a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs index ce7aefed01f..ffc237e3c24 100644 --- a/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/suspicious_to_owned.rs @@ -1,11 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_diag_trait_item; use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::is_type_diagnostic_item; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::LateContext; use rustc_middle::ty::print::with_forced_trimmed_paths; -use rustc_middle::ty::{self}; use rustc_span::sym; use super::SUSPICIOUS_TO_OWNED; @@ -14,8 +14,7 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) - if let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && is_diag_trait_item(cx, method_def_id, sym::ToOwned) && let input_type = cx.typeck_results().expr_ty(expr) - && let ty::Adt(adt, _) = cx.typeck_results().expr_ty(expr).kind() - && cx.tcx.is_diagnostic_item(sym::Cow, adt.did()) + && is_type_diagnostic_item(cx, input_type, sym::Cow) { let mut app = Applicability::MaybeIncorrect; let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0; diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs index 413881d5ec9..b87d81b7102 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -22,7 +22,8 @@ pub(super) fn check<'tcx>( let typeck_results = cx.typeck_results(); let ecx = ConstEvalCtxt::with_env(cx.tcx, cx.typing_env(), typeck_results); if let Some(id) = typeck_results.type_dependent_def_id(expr.hir_id) - && (cx.tcx.is_diagnostic_item(sym::cmp_ord_min, id) || cx.tcx.is_diagnostic_item(sym::cmp_ord_max, id)) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(id) + && matches!(fn_name, sym::cmp_ord_min | sym::cmp_ord_max) { if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(recv) && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = ecx.eval_with_source(arg) diff --git a/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs b/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs index af466fe091c..af4ade3cc0f 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unused_enumerate_index.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{SpanRangeExt, snippet}; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{expr_or_init, is_trait_method, pat_is_wild}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::AdtDef; use rustc_span::{Span, sym}; use crate::loops::UNUSED_ENUMERATE_INDEX; @@ -39,9 +39,8 @@ use crate::loops::UNUSED_ENUMERATE_INDEX; /// * `closure_arg`: The argument to the map function call containing the closure/function to apply pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { let recv_ty = cx.typeck_results().expr_ty(recv); - if let Some(recv_ty_defid) = recv_ty.ty_adt_def().map(AdtDef::did) - // If we call a method on a `std::iter::Enumerate` instance - && cx.tcx.is_diagnostic_item(sym::Enumerate, recv_ty_defid) + // If we call a method on a `std::iter::Enumerate` instance + if is_type_diagnostic_item(cx, recv_ty, sym::Enumerate) // If we are calling a method of the `Iterator` trait && is_trait_method(cx, call_expr, sym::Iterator) // And the map argument is a closure diff --git a/src/tools/clippy/clippy_lints/src/missing_inline.rs b/src/tools/clippy/clippy_lints/src/missing_inline.rs index d02952eb487..28555a61090 100644 --- a/src/tools/clippy/clippy_lints/src/missing_inline.rs +++ b/src/tools/clippy/clippy_lints/src/missing_inline.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::{span_lint, span_lint_hir}; use rustc_hir::attrs::AttributeKind; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; @@ -64,14 +64,20 @@ declare_clippy_lint! { "detects missing `#[inline]` attribute for public callables (functions, trait methods, methods...)" } -fn check_missing_inline_attrs(cx: &LateContext<'_>, attrs: &[Attribute], sp: Span, desc: &'static str) { +fn check_missing_inline_attrs( + cx: &LateContext<'_>, + attrs: &[Attribute], + sp: Span, + desc: &'static str, + hir_id: Option<hir::HirId>, +) { if !find_attr!(attrs, AttributeKind::Inline(..)) { - span_lint( - cx, - MISSING_INLINE_IN_PUBLIC_ITEMS, - sp, - format!("missing `#[inline]` for {desc}"), - ); + let msg = format!("missing `#[inline]` for {desc}"); + if let Some(hir_id) = hir_id { + span_lint_hir(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, hir_id, sp, msg); + } else { + span_lint(cx, MISSING_INLINE_IN_PUBLIC_ITEMS, sp, msg); + } } } @@ -103,17 +109,9 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a function"; let attrs = cx.tcx.hir_attrs(it.hir_id()); - check_missing_inline_attrs(cx, attrs, it.span, desc); + check_missing_inline_attrs(cx, attrs, it.span, desc, None); }, - hir::ItemKind::Trait( - ref _constness, - ref _is_auto, - ref _unsafe, - _ident, - _generics, - _bounds, - trait_items, - ) => { + hir::ItemKind::Trait(.., trait_items) => { // note: we need to check if the trait is exported so we can't use // `LateLintPass::check_trait_item` here. for &tit in trait_items { @@ -127,7 +125,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { let desc = "a default trait method"; let item = cx.tcx.hir_trait_item(tit); let attrs = cx.tcx.hir_attrs(item.hir_id()); - check_missing_inline_attrs(cx, attrs, item.span, desc); + check_missing_inline_attrs(cx, attrs, item.span, desc, Some(tit.hir_id())); } }, } @@ -182,7 +180,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } let attrs = cx.tcx.hir_attrs(impl_item.hir_id()); - check_missing_inline_attrs(cx, attrs, impl_item.span, desc); + check_missing_inline_attrs(cx, attrs, impl_item.span, desc, None); } } diff --git a/src/tools/clippy/clippy_lints/src/mut_reference.rs b/src/tools/clippy/clippy_lints/src/mut_reference.rs index 31f51b45754..ec93ef97cfa 100644 --- a/src/tools/clippy/clippy_lints/src/mut_reference.rs +++ b/src/tools/clippy/clippy_lints/src/mut_reference.rs @@ -1,4 +1,6 @@ -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; @@ -83,13 +85,18 @@ fn check_arguments<'tcx>( let parameters = type_definition.fn_sig(cx.tcx).skip_binder().inputs(); for (argument, parameter) in iter::zip(arguments, parameters) { if let ty::Ref(_, _, Mutability::Not) | ty::RawPtr(_, Mutability::Not) = parameter.kind() - && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, _) = argument.kind + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, arg) = argument.kind { - span_lint( + let mut applicability = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_applicability(cx, arg, "_", &mut applicability).addr(); + span_lint_and_sugg( cx, UNNECESSARY_MUT_PASSED, argument.span, format!("the {fn_kind} `{name}` doesn't need a mutable reference"), + "remove this `mut`", + sugg.to_string(), + applicability, ); } } diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs index 6ae26156bc4..854e927aa2f 100644 --- a/src/tools/clippy/clippy_lints/src/needless_bool.rs +++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs @@ -2,16 +2,14 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, - is_receiver_of_method_call, peel_blocks, peel_blocks_with_stmt, span_contains_comment, sym, + SpanlessEq, get_parent_expr, higher, is_block_like, is_else_clause, is_parent_stmt, is_receiver_of_method_call, + peel_blocks, peel_blocks_with_stmt, span_contains_comment, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does @@ -52,31 +50,6 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for expressions of the form `x == true`, - /// `x != true` and order comparisons such as `x < true` (or vice versa) and - /// suggest using the variable directly. - /// - /// ### Why is this bad? - /// Unnecessary code. - /// - /// ### Example - /// ```rust,ignore - /// if x == true {} - /// if y == false {} - /// ``` - /// use `x` directly: - /// ```rust,ignore - /// if x {} - /// if !y {} - /// ``` - #[clippy::version = "pre 1.29.0"] - pub BOOL_COMPARISON, - complexity, - "comparing a variable to a boolean, e.g., `if x == true` or `if x != true`" -} - -declare_clippy_lint! { - /// ### What it does /// Checks for expressions of the form `if c { x = true } else { x = false }` /// (or vice versa) and suggest assigning the variable directly from the /// condition. @@ -224,201 +197,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBool { } } -declare_lint_pass!(BoolComparison => [BOOL_COMPARISON]); - -impl<'tcx> LateLintPass<'tcx> for BoolComparison { - fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if e.span.from_expansion() { - return; - } - - if let ExprKind::Binary(Spanned { node, .. }, ..) = e.kind { - let ignore_case = None::<(fn(_) -> _, &str)>; - let ignore_no_literal = None::<(fn(_, _) -> _, &str)>; - match node { - BinOpKind::Eq => { - let true_case = Some((|h| h, "equality checks against true are unnecessary")); - let false_case = Some(( - |h: Sugg<'tcx>| !h, - "equality checks against false can be replaced by a negation", - )); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Ne => { - let true_case = Some(( - |h: Sugg<'tcx>| !h, - "inequality checks against true can be replaced by a negation", - )); - let false_case = Some((|h| h, "inequality checks against false are unnecessary")); - check_comparison(cx, e, true_case, false_case, true_case, false_case, ignore_no_literal); - }, - BinOpKind::Lt => check_comparison( - cx, - e, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| (!l).bit_and(&r), - "order comparisons between booleans can be simplified", - )), - ), - BinOpKind::Gt => check_comparison( - cx, - e, - Some(( - |h: Sugg<'tcx>| !h, - "less than comparison against true can be replaced by a negation", - )), - ignore_case, - ignore_case, - Some((|h| h, "greater than checks against false are unnecessary")), - Some(( - |l: Sugg<'tcx>, r: Sugg<'tcx>| l.bit_and(&(!r)), - "order comparisons between booleans can be simplified", - )), - ), - _ => (), - } - } - } -} - -struct ExpressionInfoWithSpan { - one_side_is_unary_not: bool, - left_span: Span, - right_span: Span, -} - -fn is_unary_not(e: &Expr<'_>) -> (bool, Span) { - if let ExprKind::Unary(UnOp::Not, operand) = e.kind { - return (true, operand.span); - } - (false, e.span) -} - -fn one_side_is_unary_not<'tcx>(left_side: &'tcx Expr<'_>, right_side: &'tcx Expr<'_>) -> ExpressionInfoWithSpan { - let left = is_unary_not(left_side); - let right = is_unary_not(right_side); - - ExpressionInfoWithSpan { - one_side_is_unary_not: left.0 != right.0, - left_span: left.1, - right_span: right.1, - } -} - -fn check_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - e: &'tcx Expr<'_>, - left_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - left_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_true: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - right_false: Option<(impl FnOnce(Sugg<'a>) -> Sugg<'a>, &'static str)>, - no_literal: Option<(impl FnOnce(Sugg<'a>, Sugg<'a>) -> Sugg<'a>, &'static str)>, -) { - if let ExprKind::Binary(op, left_side, right_side) = e.kind { - let (l_ty, r_ty) = ( - cx.typeck_results().expr_ty(left_side), - cx.typeck_results().expr_ty(right_side), - ); - if is_expn_of(left_side.span, sym::cfg).is_some() || is_expn_of(right_side.span, sym::cfg).is_some() { - return; - } - if l_ty.is_bool() && r_ty.is_bool() { - let mut applicability = Applicability::MachineApplicable; - // Eliminate parentheses in `e` by using the lo pos of lhs and hi pos of rhs, - // calling `source_callsite` make sure macros are handled correctly, see issue #9907 - let binop_span = left_side - .span - .source_callsite() - .with_hi(right_side.span.source_callsite().hi()); - - if op.node == BinOpKind::Eq { - let expression_info = one_side_is_unary_not(left_side, right_side); - if expression_info.one_side_is_unary_not { - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - "this comparison might be written more concisely", - "try simplifying it as shown", - format!( - "{} != {}", - snippet_with_applicability( - cx, - expression_info.left_span.source_callsite(), - "..", - &mut applicability - ), - snippet_with_applicability( - cx, - expression_info.right_span.source_callsite(), - "..", - &mut applicability - ) - ), - applicability, - ); - } - } - - match (fetch_bool_expr(left_side), fetch_bool_expr(right_side)) { - (Some(true), None) => left_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(true)) => right_true.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (Some(false), None) => left_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, right_side, applicability, m, h); - }), - (None, Some(false)) => right_false.map_or((), |(h, m)| { - suggest_bool_comparison(cx, binop_span, left_side, applicability, m, h); - }), - (None, None) => no_literal.map_or((), |(h, m)| { - let left_side = Sugg::hir_with_applicability(cx, left_side, "..", &mut applicability); - let right_side = Sugg::hir_with_applicability(cx, right_side, "..", &mut applicability); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - binop_span, - m, - "try simplifying it as shown", - h(left_side, right_side).into_string(), - applicability, - ); - }), - _ => (), - } - } - } -} - -fn suggest_bool_comparison<'a, 'tcx>( - cx: &LateContext<'tcx>, - span: Span, - expr: &Expr<'_>, - mut app: Applicability, - message: &'static str, - conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>, -) { - let hint = Sugg::hir_with_context(cx, expr, span.ctxt(), "..", &mut app); - span_lint_and_sugg( - cx, - BOOL_COMPARISON, - span, - message, - "try simplifying it as shown", - conv_hint(hint).into_string(), - app, - ); -} - enum Expression { Bool(bool), RetBool(bool), diff --git a/src/tools/clippy/clippy_lints/src/needless_for_each.rs b/src/tools/clippy/clippy_lints/src/needless_for_each.rs index a67545e419c..3a6ccc2bca9 100644 --- a/src/tools/clippy/clippy_lints/src/needless_for_each.rs +++ b/src/tools/clippy/clippy_lints/src/needless_for_each.rs @@ -1,6 +1,6 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind}; +use rustc_hir::{Block, BlockCheckMode, Closure, Expr, ExprKind, Stmt, StmtKind, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -70,12 +70,24 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { && has_iter_method(cx, cx.typeck_results().expr_ty(iter_recv)).is_some() // Skip the lint if the body is not block because this is simpler than `for` loop. // e.g. `v.iter().for_each(f)` is simpler and clearer than using `for` loop. - && let ExprKind::Closure(&Closure { body, .. }) = for_each_arg.kind + && let ExprKind::Closure(&Closure { body, fn_decl, .. }) = for_each_arg.kind && let body = cx.tcx.hir_body(body) // Skip the lint if the body is not safe, so as not to suggest `for … in … unsafe {}` // and suggesting `for … in … { unsafe { } }` is a little ugly. && !matches!(body.value.kind, ExprKind::Block(Block { rules: BlockCheckMode::UnsafeBlock(_), .. }, ..)) { + let mut applicability = Applicability::MachineApplicable; + + // If any closure parameter has an explicit type specified, applying the lint would necessarily + // remove that specification, possibly breaking type inference + if fn_decl + .inputs + .iter() + .any(|input| matches!(input.kind, TyKind::Infer(..))) + { + applicability = Applicability::MaybeIncorrect; + } + let mut ret_collector = RetCollector::default(); ret_collector.visit_expr(body.value); @@ -84,18 +96,16 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessForEach { return; } - let (mut applicability, ret_suggs) = if ret_collector.spans.is_empty() { - (Applicability::MachineApplicable, None) + let ret_suggs = if ret_collector.spans.is_empty() { + None } else { - ( - Applicability::MaybeIncorrect, - Some( - ret_collector - .spans - .into_iter() - .map(|span| (span, "continue".to_string())) - .collect(), - ), + applicability = Applicability::MaybeIncorrect; + Some( + ret_collector + .spans + .into_iter() + .map(|span| (span, "continue".to_string())) + .collect(), ) }; diff --git a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs index 04b09276966..ba67dc62abb 100644 --- a/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs +++ b/src/tools/clippy/clippy_lints/src/non_canonical_impls.rs @@ -134,7 +134,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { return; } - if cx.tcx.is_diagnostic_item(sym::Clone, trait_impl.def_id) + let trait_name = cx.tcx.get_diagnostic_name(trait_impl.def_id); + if trait_name == Some(sym::Clone) && let Some(copy_def_id) = cx.tcx.get_diagnostic_item(sym::Copy) && implements_trait(cx, trait_impl.self_ty(), copy_def_id, &[]) { @@ -170,12 +171,8 @@ impl LateLintPass<'_> for NonCanonicalImpls { String::new(), Applicability::MaybeIncorrect, ); - - return; } - } - - if cx.tcx.is_diagnostic_item(sym::PartialOrd, trait_impl.def_id) + } else if trait_name == Some(sym::PartialOrd) && impl_item.ident.name == sym::partial_cmp && let Some(ord_def_id) = cx.tcx.get_diagnostic_item(sym::Ord) && implements_trait(cx, trait_impl.self_ty(), ord_def_id, &[]) diff --git a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs index 23a1622f30f..cb934466bd8 100644 --- a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs +++ b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,13 +43,11 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym::mode - && matches!( - cx.tcx.get_diagnostic_name(adt.did()), - Some(sym::FsOpenOptions | sym::DirBuilder) - )) - || (path.ident.name == sym::set_mode - && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) + && matches!( + (cx.tcx.get_diagnostic_name(adt.did()), path.ident.name), + (Some(sym::FsOpenOptions | sym::DirBuilder), sym::mode) + | (Some(sym::FsPermissions), sym::set_mode) + ) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) && param diff --git a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs index 7317c62df7f..2d303e40bd1 100644 --- a/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs +++ b/src/tools/clippy/clippy_lints/src/operators/assign_op_pattern.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; +use clippy_utils::qualify_min_const_fn::is_stable_const_fn; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::visitors::for_each_expr_without_closures; @@ -20,7 +21,7 @@ pub(super) fn check<'tcx>( expr: &'tcx hir::Expr<'_>, assignee: &'tcx hir::Expr<'_>, e: &'tcx hir::Expr<'_>, - _msrv: Msrv, + msrv: Msrv, ) { if let hir::ExprKind::Binary(op, l, r) = &e.kind { let lint = |assignee: &hir::Expr<'_>, rhs: &hir::Expr<'_>| { @@ -43,10 +44,28 @@ pub(super) fn check<'tcx>( } } - // Skip if the trait is not stable in const contexts - // FIXME: reintroduce a better check after this is merged back into Clippy + // Skip if the trait or the implementation is not stable in const contexts if is_in_const_context(cx) { - return; + if cx + .tcx + .associated_item_def_ids(trait_id) + .first() + .is_none_or(|binop_id| !is_stable_const_fn(cx, *binop_id, msrv)) + { + return; + } + + let impls = cx.tcx.non_blanket_impls_for_ty(trait_id, rty).collect::<Vec<_>>(); + if impls.is_empty() + || impls.into_iter().any(|impl_id| { + cx.tcx + .associated_item_def_ids(impl_id) + .first() + .is_none_or(|fn_id| !is_stable_const_fn(cx, *fn_id, msrv)) + }) + { + return; + } } span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs index 22ec4fe60fb..604f8f5da0b 100644 --- a/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs +++ b/src/tools/clippy/clippy_lints/src/operators/cmp_owned.rs @@ -47,14 +47,10 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) (arg, arg.span) }, ExprKind::Call(path, [arg]) - if path_def_id(cx, path).is_some_and(|did| { - if cx.tcx.is_diagnostic_item(sym::from_str_method, did) { - true - } else if cx.tcx.is_diagnostic_item(sym::from_fn, did) { - !is_copy(cx, typeck.expr_ty(expr)) - } else { - false - } + if path_def_id(cx, path).is_some_and(|did| match cx.tcx.get_diagnostic_name(did) { + Some(sym::from_str_method) => true, + Some(sym::from_fn) => !is_copy(cx, typeck.expr_ty(expr)), + _ => false, }) => { (arg, arg.span) diff --git a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs index 047a5a0159c..17fa8017c97 100644 --- a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs +++ b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs @@ -34,10 +34,11 @@ pub(crate) fn check<'tcx>( val_r, ) = lhs.kind - // right hand side matches either f32::EPSILON or f64::EPSILON + // right hand side matches _::EPSILON && let ExprKind::Path(ref epsilon_path) = rhs.kind && let Res::Def(DefKind::AssocConst, def_id) = cx.qpath_res(epsilon_path, rhs.hir_id) - && ([sym::f32_epsilon, sym::f64_epsilon].into_iter().any(|sym| cx.tcx.is_diagnostic_item(sym, def_id))) + && let Some(sym) = cx.tcx.get_diagnostic_name(def_id) + && matches!(sym, sym::f16_epsilon | sym::f32_epsilon | sym::f64_epsilon | sym::f128_epsilon) // values of the subtractions on the left hand side are of the type float && let t_val_l = cx.typeck_results().expr_ty(val_l) diff --git a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs index 449d3da7639..43db0085f2e 100644 --- a/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs +++ b/src/tools/clippy/clippy_lints/src/panic_unimplemented.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; +use clippy_utils::{is_in_test, is_inside_always_const_context}; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::{LateContext, LateLintPass}; @@ -99,7 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(cx, expr) { if is_panic(cx, macro_call.def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; @@ -140,7 +140,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented { && let Res::Def(DefKind::Fn, def_id) = expr_path.res && cx.tcx.is_diagnostic_item(sym::panic_any, def_id) { - if cx.tcx.hir_is_inside_const_context(expr.hir_id) + if is_inside_always_const_context(cx.tcx, expr.hir_id) || self.allow_panic_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; diff --git a/src/tools/clippy/clippy_lints/src/redundant_clone.rs b/src/tools/clippy/clippy_lints/src/redundant_clone.rs index e57b8cc2d84..1d58cdd26d8 100644 --- a/src/tools/clippy/clippy_lints/src/redundant_clone.rs +++ b/src/tools/clippy/clippy_lints/src/redundant_clone.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::fn_has_unsatisfiable_preds; use clippy_utils::mir::{LocalUsage, PossibleBorrowerMap, visit_local_usage}; use clippy_utils::source::SpanRangeExt; -use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}; +use clippy_utils::ty::{has_drop, is_copy, is_type_lang_item, walk_ptrs_ty_depth}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, FnDecl, LangItem, def_id}; @@ -96,14 +96,13 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { let (fn_def_id, arg, arg_ty, clone_ret) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); + let fn_name = cx.tcx.get_diagnostic_name(fn_def_id); + let from_borrow = cx.tcx.lang_items().get(LangItem::CloneFn) == Some(fn_def_id) - || cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) - || (cx.tcx.is_diagnostic_item(sym::to_string_method, fn_def_id) - && is_type_lang_item(cx, arg_ty, LangItem::String)); + || fn_name == Some(sym::to_owned_method) + || (fn_name == Some(sym::to_string_method) && is_type_lang_item(cx, arg_ty, LangItem::String)); - let from_deref = !from_borrow - && (cx.tcx.is_diagnostic_item(sym::path_to_pathbuf, fn_def_id) - || cx.tcx.is_diagnostic_item(sym::os_str_to_os_string, fn_def_id)); + let from_deref = !from_borrow && matches!(fn_name, Some(sym::path_to_pathbuf | sym::os_str_to_os_string)); if !from_borrow && !from_deref { continue; @@ -148,8 +147,9 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone { is_call_with_ref_arg(cx, mir, &pred_terminator.kind) && res == cloned && cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id) - && (is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) - || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString)) + && let ty::Adt(pred_arg_def, _) = pred_arg_ty.kind() + && let Some(pred_arg_name) = cx.tcx.get_diagnostic_name(pred_arg_def.did()) + && matches!(pred_arg_name, sym::PathBuf | sym::OsString) { (pred_arg, res) } else { diff --git a/src/tools/clippy/clippy_lints/src/semicolon_block.rs b/src/tools/clippy/clippy_lints/src/semicolon_block.rs index db91c57b181..1dea8f17c34 100644 --- a/src/tools/clippy/clippy_lints/src/semicolon_block.rs +++ b/src/tools/clippy/clippy_lints/src/semicolon_block.rs @@ -155,6 +155,11 @@ impl LateLintPass<'_> for SemicolonBlock { kind: ExprKind::Block(block, _), .. }) if !block.span.from_expansion() => { + let attrs = cx.tcx.hir_attrs(stmt.hir_id); + if !attrs.is_empty() && !cx.tcx.features().stmt_expr_attributes() { + return; + } + if let Some(tail) = block.expr { self.semicolon_inside_block(cx, block, tail, stmt.span); } diff --git a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs index e9534bc63a6..8c4a50041e6 100644 --- a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs +++ b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs @@ -126,6 +126,8 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports { && !is_from_proc_macro(cx, &first_segment.ident) && !matches!(def_kind, DefKind::Macro(_)) && let Some(last_segment) = path.segments.last() + && let Res::Def(DefKind::Mod, crate_def_id) = first_segment.res + && crate_def_id.is_crate_root() { let (lint, used_mod, replace_with) = match first_segment.ident.name { sym::std => match cx.tcx.crate_name(def_id.krate) { diff --git a/src/tools/clippy/clippy_lints/src/strings.rs b/src/tools/clippy/clippy_lints/src/strings.rs index 490e6c974ae..57d5900b045 100644 --- a/src/tools/clippy/clippy_lints/src/strings.rs +++ b/src/tools/clippy/clippy_lints/src/strings.rs @@ -457,7 +457,8 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { } fn is_one_of_trim_diagnostic_items(cx: &LateContext<'_>, trim_def_id: DefId) -> bool { - cx.tcx.is_diagnostic_item(sym::str_trim, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_start, trim_def_id) - || cx.tcx.is_diagnostic_item(sym::str_trim_end, trim_def_id) + matches!( + cx.tcx.get_diagnostic_name(trim_def_id), + Some(sym::str_trim | sym::str_trim_start | sym::str_trim_end) + ) } diff --git a/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs b/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs index 08f36a2ed5d..543f3c45e14 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -1,8 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use std::borrow::Cow; + +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{HasSession, SpanRangeExt as _}; use rustc_errors::Applicability; -use rustc_hir::{GenericArg, HirId, LetStmt, Node, Path, TyKind}; +use rustc_hir::{Expr, GenericArg, HirId, LetStmt, Node, Path, TyKind}; use rustc_lint::LateContext; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; use crate::transmute::MISSING_TRANSMUTE_ANNOTATIONS; @@ -38,6 +42,7 @@ fn is_function_block(cx: &LateContext<'_>, expr_hir_id: HirId) -> bool { pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, path: &Path<'tcx>, + arg: &Expr<'tcx>, from_ty: Ty<'tcx>, to_ty: Ty<'tcx>, expr_hir_id: HirId, @@ -68,14 +73,48 @@ pub(super) fn check<'tcx>( } else if is_function_block(cx, expr_hir_id) { return false; } - span_lint_and_sugg( + let span = last.ident.span.with_hi(path.span.hi()); + span_lint_and_then( cx, MISSING_TRANSMUTE_ANNOTATIONS, - last.ident.span.with_hi(path.span.hi()), + span, "transmute used without annotations", - "consider adding missing annotations", - format!("{}::<{from_ty}, {to_ty}>", last.ident), - Applicability::MaybeIncorrect, + |diag| { + let from_ty_no_name = ty_cannot_be_named(from_ty); + let to_ty_no_name = ty_cannot_be_named(to_ty); + if from_ty_no_name || to_ty_no_name { + let to_name = match (from_ty_no_name, to_ty_no_name) { + (true, false) => maybe_name_by_expr(cx, arg.span, "the origin type"), + (false, true) => "the destination type".into(), + _ => "the source and destination types".into(), + }; + diag.help(format!( + "consider giving {to_name} a name, and adding missing type annotations" + )); + } else { + diag.span_suggestion( + span, + "consider adding missing annotations", + format!("{}::<{from_ty}, {to_ty}>", last.ident), + Applicability::MaybeIncorrect, + ); + } + }, ); true } + +fn ty_cannot_be_named(ty: Ty<'_>) -> bool { + matches!( + ty.kind(), + ty::Alias(ty::AliasTyKind::Opaque | ty::AliasTyKind::Inherent, _) + ) +} + +fn maybe_name_by_expr<'a>(sess: &impl HasSession, span: Span, default: &'a str) -> Cow<'a, str> { + span.with_source_text(sess, |name| { + (name.len() + 9 < default.len()).then_some(format!("`{name}`'s type").into()) + }) + .flatten() + .unwrap_or(default.into()) +} diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs index 1c7bb4314dd..5fda388259a 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs @@ -520,7 +520,7 @@ impl<'tcx> LateLintPass<'tcx> for Transmute { | transmuting_null::check(cx, e, arg, to_ty) | transmute_null_to_fn::check(cx, e, arg, to_ty) | transmute_ptr_to_ref::check(cx, e, from_ty, to_ty, arg, path, self.msrv) - | missing_transmute_annotations::check(cx, path, from_ty, to_ty, e.hir_id) + | missing_transmute_annotations::check(cx, path, arg, from_ty, to_ty, e.hir_id) | transmute_ref_to_ref::check(cx, e, from_ty, to_ty, arg, const_context) | transmute_ptr_to_ptr::check(cx, e, from_ty, to_ty, arg, self.msrv) | transmute_int_to_bool::check(cx, e, from_ty, to_ty, arg) diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index f27aaa2fa77..2257aa1b73c 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -1,6 +1,6 @@ use super::TRANSMUTE_INT_TO_NON_ZERO; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::sugg; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir::Expr; use rustc_lint::LateContext; @@ -16,35 +16,24 @@ pub(super) fn check<'tcx>( to_ty: Ty<'tcx>, arg: &'tcx Expr<'_>, ) -> bool { - let tcx = cx.tcx; - - let (ty::Int(_) | ty::Uint(_), ty::Adt(adt, substs)) = (&from_ty.kind(), to_ty.kind()) else { - return false; - }; - - if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { - return false; + if let ty::Int(_) | ty::Uint(_) = from_ty.kind() + && let ty::Adt(adt, substs) = to_ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_ty = substs.type_at(0) + && from_ty == int_ty + { + let arg = Sugg::hir(cx, arg, ".."); + span_lint_and_sugg( + cx, + TRANSMUTE_INT_TO_NON_ZERO, + e.span, + format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), + "consider using", + format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), + Applicability::Unspecified, + ); + true + } else { + false } - - let int_ty = substs.type_at(0); - if from_ty != int_ty { - return false; - } - - span_lint_and_then( - cx, - TRANSMUTE_INT_TO_NON_ZERO, - e.span, - format!("transmute from a `{from_ty}` to a `{}<{int_ty}>`", sym::NonZero), - |diag| { - let arg = sugg::Sugg::hir(cx, arg, ".."); - diag.span_suggestion( - e.span, - "consider using", - format!("{}::{}({arg})", sym::NonZero, sym::new_unchecked), - Applicability::Unspecified, - ); - }, - ); - true } diff --git a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs index d691f1878b1..c4fd0fbf87a 100644 --- a/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs +++ b/src/tools/clippy/clippy_lints/src/types/rc_buffer.rs @@ -11,7 +11,8 @@ use super::RC_BUFFER; pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { let app = Applicability::Unspecified; - if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { + let name = cx.tcx.get_diagnostic_name(def_id); + if name == Some(sym::Rc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( @@ -56,7 +57,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ ); return true; } - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { + } else if name == Some(sym::Arc) { if let Some(alternate) = match_buffer_type(cx, qpath) { #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] span_lint_and_then( diff --git a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs index de3456a8ba5..0ba51daf027 100644 --- a/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs +++ b/src/tools/clippy/clippy_lints/src/types/redundant_allocation.rs @@ -13,14 +13,11 @@ use super::{REDUNDANT_ALLOCATION, utils}; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'tcx>, qpath: &QPath<'tcx>, def_id: DefId) -> bool { let mut applicability = Applicability::MaybeIncorrect; - let outer_sym = if Some(def_id) == cx.tcx.lang_items().owned_box() { - "Box" - } else if cx.tcx.is_diagnostic_item(sym::Rc, def_id) { - "Rc" - } else if cx.tcx.is_diagnostic_item(sym::Arc, def_id) { - "Arc" - } else { - return false; + let outer_sym = match cx.tcx.get_diagnostic_name(def_id) { + _ if Some(def_id) == cx.tcx.lang_items().owned_box() => "Box", + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, }; if let Some(span) = utils::match_borrows_parameter(cx, qpath) { diff --git a/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs b/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs index 48b532968cb..38716519e23 100644 --- a/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs +++ b/src/tools/clippy/clippy_lints/src/unit_types/unit_cmp.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint; use clippy_utils::macros::{find_assert_eq_args, root_macro_call_first_node}; -use clippy_utils::sym; +use clippy_utils::{is_unit_expr, sym}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::LateContext; @@ -16,10 +16,13 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { sym::assert_ne_macro | sym::debug_assert_ne_macro => "fail", _ => return, }; - let Some((left, _, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { + let Some((lhs, rhs, _)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return; }; - if !cx.typeck_results().expr_ty(left).is_unit() { + if is_unit_expr(lhs) || is_unit_expr(rhs) { + return; + } + if !cx.typeck_results().expr_ty(lhs).is_unit() { return; } span_lint( diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs index 7d996775a58..28f4884fa31 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_owned_empty_strings.rs @@ -41,7 +41,8 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { && let ty::Ref(_, inner_str, _) = cx.typeck_results().expr_ty_adjusted(expr).kind() && inner_str.is_str() { - if cx.tcx.is_diagnostic_item(sym::string_new, fun_def_id) { + let fun_name = cx.tcx.get_diagnostic_name(fun_def_id); + if fun_name == Some(sym::string_new) { span_lint_and_sugg( cx, UNNECESSARY_OWNED_EMPTY_STRINGS, @@ -51,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryOwnedEmptyStrings { "\"\"".to_owned(), Applicability::MachineApplicable, ); - } else if cx.tcx.is_diagnostic_item(sym::from_fn, fun_def_id) + } else if fun_name == Some(sym::from_fn) && let [arg] = args && let ExprKind::Lit(spanned) = &arg.kind && let LitKind::Str(symbol, _) = spanned.node diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs index 54a7efc090a..849c0b438a5 100644 --- a/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs +++ b/src/tools/clippy/clippy_lints/src/unnecessary_wraps.rs @@ -107,12 +107,10 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { // Get the wrapper and inner types, if can't, abort. let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() { - if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { - ("Option", OptionSome, subst.type_at(0)) - } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { - ("Result", ResultOk, subst.type_at(0)) - } else { - return; + match cx.tcx.get_diagnostic_name(adt_def.did()) { + Some(sym::Option) => ("Option", OptionSome, subst.type_at(0)), + Some(sym::Result) => ("Result", ResultOk, subst.type_at(0)), + _ => return, } } else { return; diff --git a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs index 8b278d98a30..f3410c98973 100644 --- a/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs +++ b/src/tools/clippy/clippy_lints/src/unnested_or_patterns.rs @@ -326,7 +326,11 @@ fn extend_with_struct_pat( if idx_1 == idx { // In the case of `k`, we merely require identical field names // so that we will transform into `ident_k: p1_k | p2_k`. - let pos = fps2.iter().position(|fp2| eq_id(fp1.ident, fp2.ident)); + let pos = fps2.iter().position(|fp2| { + // Avoid `Foo { bar } | Foo { bar }` => `Foo { bar | bar }` + !(fp1.is_shorthand && fp2.is_shorthand) + && eq_id(fp1.ident, fp2.ident) + }); pos_in_2.set(pos); pos.is_some() } else { diff --git a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs index f3cd3f1bb28..af3ad4566c4 100644 --- a/src/tools/clippy/clippy_lints/src/unused_io_amount.rs +++ b/src/tools/clippy/clippy_lints/src/unused_io_amount.rs @@ -89,7 +89,9 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { { // We don't want to lint inside io::Read or io::Write implementations, as the author has more // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 - if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + if let Some(trait_name) = cx.tcx.get_diagnostic_name(trait_id) + && matches!(trait_name, sym::IoRead | sym::IoWrite) + { return; } diff --git a/src/tools/clippy/clippy_lints/src/unused_unit.rs b/src/tools/clippy/clippy_lints/src/unused_unit.rs index 3811f0fe6b5..d503bd3379b 100644 --- a/src/tools/clippy/clippy_lints/src/unused_unit.rs +++ b/src/tools/clippy/clippy_lints/src/unused_unit.rs @@ -6,13 +6,13 @@ use rustc_errors::Applicability; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, Node, PolyTraitRef, Term, - Ty, TyKind, + AssocItemConstraintKind, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArgsParentheses, PolyTraitRef, Term, Ty, + TyKind, }; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::edition::Edition; -use rustc_span::{BytePos, Span, sym}; +use rustc_span::{BytePos, Pos as _, Span, sym}; declare_clippy_lint! { /// ### What it does @@ -49,19 +49,22 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { decl: &'tcx FnDecl<'tcx>, body: &'tcx Body<'tcx>, span: Span, - def_id: LocalDefId, + _def_id: LocalDefId, ) { if let FnRetTy::Return(hir_ty) = decl.output && is_unit_ty(hir_ty) && !hir_ty.span.from_expansion() && get_def(span) == get_def(hir_ty.span) { - // implicit types in closure signatures are forbidden when `for<...>` is present - if let FnKind::Closure = kind - && let Node::Expr(expr) = cx.tcx.hir_node_by_def_id(def_id) - && let ExprKind::Closure(closure) = expr.kind - && !closure.bound_generic_params.is_empty() - { + // The explicit `-> ()` in the closure signature might be necessary for multiple reasons: + // - Implicit types in closure signatures are forbidden when `for<...>` is present + // - If the closure body ends with a function call, and that function's return type is generic, the + // `-> ()` could be required for it to be inferred + // + // There could be more reasons to have it, and, in general, we shouldn't discourage the users from + // writing more type annotations than strictly necessary, because it can help readability and + // maintainability + if let FnKind::Closure = kind { return; } @@ -97,16 +100,13 @@ impl<'tcx> LateLintPass<'tcx> for UnusedUnit { } fn check_poly_trait_ref(&mut self, cx: &LateContext<'tcx>, poly: &'tcx PolyTraitRef<'tcx>) { - let segments = &poly.trait_ref.path.segments; - - if segments.len() == 1 - && matches!(segments[0].ident.name, sym::Fn | sym::FnMut | sym::FnOnce) - && let Some(args) = segments[0].args + if let [segment] = &poly.trait_ref.path.segments + && matches!(segment.ident.name, sym::Fn | sym::FnMut | sym::FnOnce) + && let Some(args) = segment.args && args.parenthesized == GenericArgsParentheses::ParenSugar - && let constraints = &args.constraints - && constraints.len() == 1 - && constraints[0].ident.name == sym::Output - && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraints[0].kind + && let [constraint] = &args.constraints + && constraint.ident.name == sym::Output + && let AssocItemConstraintKind::Equality { term: Term::Ty(hir_ty) } = constraint.kind && args.span_ext.hi() != poly.span.hi() && !hir_ty.span.from_expansion() && args.span_ext.hi() != hir_ty.span.hi() @@ -160,17 +160,15 @@ fn get_def(span: Span) -> Option<Span> { fn lint_unneeded_unit_return(cx: &LateContext<'_>, ty_span: Span, span: Span) { let (ret_span, appl) = - span.with_hi(ty_span.hi()) - .get_source_text(cx) - .map_or((ty_span, Applicability::MaybeIncorrect), |src| { - position_before_rarrow(&src).map_or((ty_span, Applicability::MaybeIncorrect), |rpos| { - ( - #[expect(clippy::cast_possible_truncation)] - ty_span.with_lo(BytePos(span.lo().0 + rpos as u32)), - Applicability::MachineApplicable, - ) - }) - }); + if let Some(Some(rpos)) = span.with_hi(ty_span.hi()).with_source_text(cx, position_before_rarrow) { + ( + ty_span.with_lo(span.lo() + BytePos::from_usize(rpos)), + Applicability::MachineApplicable, + ) + } else { + (ty_span, Applicability::MaybeIncorrect) + }; + span_lint_and_sugg( cx, UNUSED_UNIT, diff --git a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs index 7bec212a23c..f26647fa348 100644 --- a/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs +++ b/src/tools/clippy/clippy_lints/src/unwrap_in_result.rs @@ -1,13 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::visitors::for_each_expr; -use clippy_utils::{method_chain_args, return_ty}; -use core::ops::ControlFlow; -use rustc_hir as hir; -use rustc_hir::ImplItemKind; +use clippy_utils::{return_ty, sym}; +use rustc_hir::{ + Body, BodyOwnerKind, Expr, ExprKind, FnSig, ImplItem, ImplItemKind, Item, ItemKind, OwnerId, PathSegment, QPath, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::{Span, sym}; +use rustc_middle::ty::Ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Ident, Span, Symbol}; declare_clippy_lint! { /// ### What it does @@ -57,62 +56,165 @@ declare_clippy_lint! { "functions of type `Result<..>` or `Option`<...> that contain `expect()` or `unwrap()`" } -declare_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); +impl_lint_pass!(UnwrapInResult=> [UNWRAP_IN_RESULT]); -impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(ref _signature, _) = impl_item.kind - // first check if it's a method or function - // checking if its return type is `result` or `option` - && (is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Result) - || is_type_diagnostic_item(cx, return_ty(cx, impl_item.owner_id), sym::Option)) - { - lint_impl_body(cx, impl_item.span, impl_item); +#[derive(Clone, Copy, Eq, PartialEq)] +enum OptionOrResult { + Option, + Result, +} + +impl OptionOrResult { + fn with_article(self) -> &'static str { + match self { + Self::Option => "an `Option`", + Self::Result => "a `Result`", } } } -fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_item: &'tcx hir::ImplItem<'_>) { - if let ImplItemKind::Fn(_, body_id) = impl_item.kind { - let body = cx.tcx.hir_body(body_id); - let typeck = cx.tcx.typeck(impl_item.owner_id.def_id); - let mut result = Vec::new(); - let _: Option<!> = for_each_expr(cx, body.value, |e| { - // check for `expect` - if let Some(arglists) = method_chain_args(e, &[sym::expect]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } - - // check for `unwrap` - if let Some(arglists) = method_chain_args(e, &[sym::unwrap]) { - let receiver_ty = typeck.expr_ty(arglists[0].0).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::Option) - || is_type_diagnostic_item(cx, receiver_ty, sym::Result) - { - result.push(e.span); - } - } - - ControlFlow::Continue(()) +struct OptionOrResultFn { + kind: OptionOrResult, + return_ty_span: Option<Span>, +} + +#[derive(Default)] +pub struct UnwrapInResult { + fn_stack: Vec<Option<OptionOrResultFn>>, + current_fn: Option<OptionOrResultFn>, +} + +impl UnwrapInResult { + fn enter_item(&mut self, cx: &LateContext<'_>, fn_def_id: OwnerId, sig: &FnSig<'_>) { + self.fn_stack.push(self.current_fn.take()); + self.current_fn = is_option_or_result(cx, return_ty(cx, fn_def_id)).map(|kind| OptionOrResultFn { + kind, + return_ty_span: Some(sig.decl.output.span()), }); + } - // if we've found one, lint - if !result.is_empty() { + fn leave_item(&mut self) { + self.current_fn = self.fn_stack.pop().unwrap(); + } +} + +impl<'tcx> LateLintPass<'tcx> for UnwrapInResult { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + if let ImplItemKind::Fn(sig, _) = &impl_item.kind { + self.enter_item(cx, impl_item.owner_id, sig); + } + } + + fn check_impl_item_post(&mut self, _: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'tcx>) { + if let ImplItemKind::Fn(..) = impl_item.kind { + self.leave_item(); + } + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { + has_body: true, sig, .. + } = &item.kind + { + self.enter_item(cx, item.owner_id, sig); + } + } + + fn check_item_post(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Fn { has_body: true, .. } = item.kind { + self.leave_item(); + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { + if expr.span.from_expansion() { + return; + } + + if let Some(OptionOrResultFn { + kind, + ref mut return_ty_span, + }) = self.current_fn + && let Some((oor, fn_name)) = is_unwrap_or_expect_call(cx, expr) + && oor == kind + { span_lint_and_then( cx, UNWRAP_IN_RESULT, - impl_span, - "used unwrap or expect in a function that returns result or option", - move |diag| { - diag.help("unwrap and expect should not be used in a function that returns result or option"); - diag.span_note(result, "potential non-recoverable error(s)"); + expr.span, + format!("`{fn_name}` used in a function that returns {}", kind.with_article()), + |diag| { + // Issue the note and help only once per function + if let Some(span) = return_ty_span.take() { + diag.span_note(span, "in this function signature"); + let complement = if kind == OptionOrResult::Result { + " or calling the `.map_err()` method" + } else { + "" + }; + diag.help(format!("consider using the `?` operator{complement}")); + } }, ); } } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // When entering a body which is not a function, mask the potential surrounding + // function to not apply the lint. + self.fn_stack.push(self.current_fn.take()); + } + } + + fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let body_def_id = cx.tcx.hir_body_owner_def_id(body.id()); + if !matches!(cx.tcx.hir_body_owner_kind(body_def_id), BodyOwnerKind::Fn) { + // Unmask the potential surrounding function. + self.current_fn = self.fn_stack.pop().unwrap(); + } + } +} + +fn is_option_or_result(cx: &LateContext<'_>, ty: Ty<'_>) -> Option<OptionOrResult> { + match ty.ty_adt_def().and_then(|def| cx.tcx.get_diagnostic_name(def.did())) { + Some(sym::Option) => Some(OptionOrResult::Option), + Some(sym::Result) => Some(OptionOrResult::Result), + _ => None, + } +} + +fn is_unwrap_or_expect_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(OptionOrResult, Symbol)> { + if let ExprKind::Call(func, _) = expr.kind + && let ExprKind::Path(QPath::TypeRelative( + hir_ty, + PathSegment { + ident: + Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + )) = func.kind + { + is_option_or_result(cx, cx.typeck_results().node_type(hir_ty.hir_id)).map(|oor| (oor, *name)) + } else if let ExprKind::MethodCall( + PathSegment { + ident: Ident { + name: name @ (sym::unwrap | sym::expect), + .. + }, + .. + }, + recv, + _, + _, + ) = expr.kind + { + is_option_or_result(cx, cx.typeck_results().expr_ty_adjusted(recv)).map(|oor| (oor, *name)) + } else { + None + } } diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs index e5b20c0e0a1..70ae982a445 100644 --- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs +++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs @@ -386,12 +386,13 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { ExprKind::Call(path, [arg]) => { if let ExprKind::Path(ref qpath) = path.kind - && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && !is_ty_alias(qpath) + && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() + && let Some(name) = cx.tcx.get_diagnostic_name(def_id) { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(arg); - if cx.tcx.is_diagnostic_item(sym::try_from_fn, def_id) + if name == sym::try_from_fn && is_type_diagnostic_item(cx, a, sym::Result) && let ty::Adt(_, args) = a.kind() && let Some(a_type) = args.types().next() @@ -406,9 +407,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { None, hint, ); - } - - if cx.tcx.is_diagnostic_item(sym::from_fn, def_id) && same_type_and_consts(a, b) { + } else if name == sym::from_fn && same_type_and_consts(a, b) { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_paren(); let sugg_msg = format!("consider removing `{}()`", snippet(cx, path.span, "From::from")); diff --git a/src/tools/clippy/clippy_lints/src/vec.rs b/src/tools/clippy/clippy_lints/src/vec.rs index 7b6a25123e8..52b30ddce12 100644 --- a/src/tools/clippy/clippy_lints/src/vec.rs +++ b/src/tools/clippy/clippy_lints/src/vec.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::collections::btree_map::Entry; +use std::mem; use std::ops::ControlFlow; use clippy_config::Conf; @@ -20,15 +22,36 @@ use rustc_span::{DesugaringKind, Span}; pub struct UselessVec { too_large_for_stack: u64, msrv: Msrv, - span_to_lint_map: BTreeMap<Span, Option<(HirId, SuggestedType, String, Applicability)>>, + /// Maps from a `vec![]` source callsite invocation span to the "state" (i.e., whether we can + /// emit a warning there or not). + /// + /// The purpose of this is to buffer lints up until `check_crate_post` so that we can cancel a + /// lint while visiting, because a `vec![]` invocation span can appear multiple times when + /// it is passed as a macro argument, once in a context that doesn't require a `Vec<_>` and + /// another time that does. Consider: + /// ``` + /// macro_rules! m { + /// ($v:expr) => { + /// let a = $v; + /// $v.push(3); + /// } + /// } + /// m!(vec![1, 2]); + /// ``` + /// The macro invocation expands to two `vec![1, 2]` invocations. If we eagerly suggest changing + /// the first `vec![1, 2]` (which is shared with the other expn) to an array which indeed would + /// work, we get a false positive warning on the `$v.push(3)` which really requires `$v` to + /// be a vector. + span_to_state: BTreeMap<Span, VecState>, allow_in_test: bool, } + impl UselessVec { pub fn new(conf: &'static Conf) -> Self { Self { too_large_for_stack: conf.too_large_for_stack, msrv: conf.msrv, - span_to_lint_map: BTreeMap::new(), + span_to_state: BTreeMap::new(), allow_in_test: conf.allow_useless_vec_in_tests, } } @@ -62,17 +85,28 @@ declare_clippy_lint! { impl_lint_pass!(UselessVec => [USELESS_VEC]); -impl<'tcx> LateLintPass<'tcx> for UselessVec { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) else { - return; - }; - if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { - return; - } - // the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!` - let callsite = expr.span.parent_callsite().unwrap_or(expr.span); +/// The "state" of a `vec![]` invocation, indicating whether it can or cannot be changed. +enum VecState { + Change { + suggest_ty: SuggestedType, + vec_snippet: String, + expr_hir_id: HirId, + }, + NoChange, +} + +enum VecToArray { + /// Expression does not need to be a `Vec<_>` and its type can be changed to an array (or + /// slice). + Possible, + /// Expression must be a `Vec<_>`. Type cannot change. + Impossible, +} +impl UselessVec { + /// Checks if the surrounding environment requires this expression to actually be of type + /// `Vec<_>`, or if it can be changed to `&[]`/`[]` without causing type errors. + fn expr_usage_requires_vec(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) -> VecToArray { match cx.tcx.parent_hir_node(expr.hir_id) { // search for `let foo = vec![_]` expressions where all uses of `foo` // adjust to slices or call a method that exist on slices (e.g. len) @@ -100,110 +134,126 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { .is_continue(); if only_slice_uses { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, SuggestedType::Array); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, // if the local pattern has a specified type, do not lint. Node::LetStmt(LetStmt { ty: Some(_), .. }) if higher::VecArgs::hir(cx, expr).is_some() => { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible }, // search for `for _ in vec![...]` Node::Expr(Expr { span, .. }) if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => { - let suggest_slice = suggest_type(expr); - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible }, // search for `&vec![_]` or `vec![_]` expressions where the adjusted type is `&[_]` _ => { - let suggest_slice = suggest_type(expr); - if adjusts_to_slice(cx, expr) { - self.check_vec_macro(cx, &vec_args, callsite, expr.hir_id, suggest_slice); + VecToArray::Possible } else { - self.span_to_lint_map.insert(callsite, None); + VecToArray::Impossible } }, } } +} + +impl<'tcx> LateLintPass<'tcx> for UselessVec { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(vec_args) = higher::VecArgs::hir(cx, expr.peel_borrows()) + // The `vec![]` or `&vec![]` invocation span. + && let vec_span = expr.span.parent_callsite().unwrap_or(expr.span) + && !vec_span.from_expansion() + { + if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { + return; + } + + match self.expr_usage_requires_vec(cx, expr) { + VecToArray::Possible => { + let suggest_ty = suggest_type(expr); + + // Size and `Copy` checks don't depend on the enclosing usage of the expression + // and don't need to be inserted into the state map. + let vec_snippet = match vec_args { + higher::VecArgs::Repeat(expr, len) => { + if is_copy(cx, cx.typeck_results().expr_ty(expr)) + && let Some(Constant::Int(length)) = ConstEvalCtxt::new(cx).eval(len) + && let Ok(length) = u64::try_from(length) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet( + cx, + Some(expr.span.source_callsite()), + Some(len.span.source_callsite()), + ) + } else { + return; + } + }, + higher::VecArgs::Vec(args) => { + if let Ok(length) = u64::try_from(args.len()) + && size_of(cx, expr) + .checked_mul(length) + .is_some_and(|size| size <= self.too_large_for_stack) + { + suggest_ty.snippet( + cx, + args.first().zip(args.last()).map(|(first, last)| { + first.span.source_callsite().to(last.span.source_callsite()) + }), + None, + ) + } else { + return; + } + }, + }; + + if let Entry::Vacant(entry) = self.span_to_state.entry(vec_span) { + entry.insert(VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id: expr.hir_id, + }); + } + }, + VecToArray::Impossible => { + self.span_to_state.insert(vec_span, VecState::NoChange); + }, + } + } + } fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { - for (span, lint_opt) in &self.span_to_lint_map { - if let Some((hir_id, suggest_slice, snippet, applicability)) = lint_opt { - let help_msg = format!("you can use {} directly", suggest_slice.desc()); - span_lint_hir_and_then(cx, USELESS_VEC, *hir_id, *span, "useless use of `vec!`", |diag| { - // If the `vec!` macro contains comment, better not make the suggestion machine - // applicable as it would remove them. - let applicability = if *applicability != Applicability::Unspecified - && let source_map = cx.tcx.sess.source_map() - && span_contains_comment(source_map, *span) - { + for (span, state) in mem::take(&mut self.span_to_state) { + if let VecState::Change { + suggest_ty, + vec_snippet, + expr_hir_id, + } = state + { + span_lint_hir_and_then(cx, USELESS_VEC, expr_hir_id, span, "useless use of `vec!`", |diag| { + let help_msg = format!("you can use {} directly", suggest_ty.desc()); + // If the `vec!` macro contains comment, better not make the suggestion machine applicable as it + // would remove them. + let applicability = if span_contains_comment(cx.tcx.sess.source_map(), span) { Applicability::Unspecified } else { - *applicability + Applicability::MachineApplicable }; - diag.span_suggestion(*span, help_msg, snippet, applicability); + diag.span_suggestion(span, help_msg, vec_snippet, applicability); }); } } } } -impl UselessVec { - fn check_vec_macro<'tcx>( - &mut self, - cx: &LateContext<'tcx>, - vec_args: &higher::VecArgs<'tcx>, - span: Span, - hir_id: HirId, - suggest_slice: SuggestedType, - ) { - if span.from_expansion() { - return; - } - - let snippet = match *vec_args { - higher::VecArgs::Repeat(elem, len) => { - if let Some(Constant::Int(len_constant)) = ConstEvalCtxt::new(cx).eval(len) { - // vec![ty; N] works when ty is Clone, [ty; N] requires it to be Copy also - if !is_copy(cx, cx.typeck_results().expr_ty(elem)) { - return; - } - - #[expect(clippy::cast_possible_truncation)] - if len_constant as u64 * size_of(cx, elem) > self.too_large_for_stack { - return; - } - - suggest_slice.snippet(cx, Some(elem.span), Some(len.span)) - } else { - return; - } - }, - higher::VecArgs::Vec(args) => { - let args_span = if let Some(last) = args.iter().last() { - if args.len() as u64 * size_of(cx, last) > self.too_large_for_stack { - return; - } - Some(args[0].span.source_callsite().to(last.span.source_callsite())) - } else { - None - }; - suggest_slice.snippet(cx, args_span, None) - }, - }; - - self.span_to_lint_map.entry(span).or_insert(Some(( - hir_id, - suggest_slice, - snippet, - Applicability::MachineApplicable, - ))); - } -} - #[derive(Copy, Clone)] pub(crate) enum SuggestedType { /// Suggest using a slice `&[..]` / `&mut [..]` @@ -221,11 +271,17 @@ impl SuggestedType { } fn snippet(self, cx: &LateContext<'_>, args_span: Option<Span>, len_span: Option<Span>) -> String { + // Invariant of the lint as implemented: all spans are from the root context (and as a result, + // always trivially crate-local). + assert!(args_span.is_none_or(|s| !s.from_expansion())); + assert!(len_span.is_none_or(|s| !s.from_expansion())); + let maybe_args = args_span - .and_then(|sp| sp.get_source_text(cx)) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) .map_or(String::new(), |x| x.to_owned()); let maybe_len = len_span - .and_then(|sp| sp.get_source_text(cx).map(|s| format!("; {s}"))) + .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) + .map(|st| format!("; {st}")) .unwrap_or_default(); match self { diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index a15e4e42e71..c55c5ec2f51 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -537,7 +537,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - if let Some((_, index)) = positional_arg_piece_span(piece) { + if let Some((_, index)) = format_arg_piece_span(piece) { replaced_position.push(index); } @@ -569,16 +569,11 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { } } -/// Extract Span and its index from the given `piece`, if it's positional argument. -fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { match piece { FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - kind: FormatArgPositionKind::Number, - .. - }, + argument: FormatArgPosition { index: Ok(index), .. }, span: Some(span), .. }) => Some((*span, *index)), diff --git a/src/tools/clippy/clippy_lints/src/zombie_processes.rs b/src/tools/clippy/clippy_lints/src/zombie_processes.rs index 6ab94a52210..a934d2094e0 100644 --- a/src/tools/clippy/clippy_lints/src/zombie_processes.rs +++ b/src/tools/clippy/clippy_lints/src/zombie_processes.rs @@ -1,5 +1,6 @@ use ControlFlow::{Break, Continue}; use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{fn_def_id, get_enclosing_block, path_to_local_id}; use rustc_ast::Mutability; use rustc_ast::visit::visit_opt; @@ -58,8 +59,8 @@ declare_lint_pass!(ZombieProcesses => [ZOMBIE_PROCESSES]); impl<'tcx> LateLintPass<'tcx> for ZombieProcesses { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let ExprKind::Call(..) | ExprKind::MethodCall(..) = expr.kind - && let Some(child_adt) = cx.typeck_results().expr_ty(expr).ty_adt_def() - && cx.tcx.is_diagnostic_item(sym::Child, child_adt.did()) + && let child_ty = cx.typeck_results().expr_ty(expr) + && is_type_diagnostic_item(cx, child_ty, sym::Child) { match cx.tcx.parent_hir_node(expr.hir_id) { Node::LetStmt(local) @@ -177,8 +178,8 @@ impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { Node::Expr(expr) if let ExprKind::AddrOf(_, Mutability::Not, _) = expr.kind => {}, Node::Expr(expr) if let Some(fn_did) = fn_def_id(self.cx, expr) - && (self.cx.tcx.is_diagnostic_item(sym::child_id, fn_did) - || self.cx.tcx.is_diagnostic_item(sym::child_kill, fn_did)) => {}, + && let Some(fn_name) = self.cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::child_id | sym::child_kill) => {}, // Conservatively assume that all other kinds of nodes call `.wait()` somehow. _ => return Break(MaybeWait(ex.span)), @@ -351,9 +352,14 @@ fn check<'tcx>(cx: &LateContext<'tcx>, spawn_expr: &'tcx Expr<'tcx>, cause: Caus /// Checks if the given expression exits the process. fn is_exit_expression(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - fn_def_id(cx, expr).is_some_and(|fn_did| { - cx.tcx.is_diagnostic_item(sym::process_exit, fn_did) || cx.tcx.is_diagnostic_item(sym::process_abort, fn_did) - }) + if let Some(fn_did) = fn_def_id(cx, expr) + && let Some(fn_name) = cx.tcx.get_diagnostic_name(fn_did) + && matches!(fn_name, sym::process_exit | sym::process_abort) + { + true + } else { + false + } } #[derive(Debug)] |
