From 0eb9f41a07ae7087d7f9ce557cbfcaa2b173c95a Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 08:43:44 +0700 Subject: [PATCH 001/107] make it more readable by faster early exitting --- clippy_lints/src/no_effect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 87f886b1128d8..dc017fa66348b 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -258,10 +258,10 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { if let StmtKind::Semi(expr) = stmt.kind + && !in_external_macro(cx.sess(), stmt.span) && let ctxt = stmt.span.ctxt() && expr.span.ctxt() == ctxt && let Some(reduced) = reduce_expression(cx, expr) - && !in_external_macro(cx.sess(), stmt.span) && reduced.iter().all(|e| e.span.ctxt() == ctxt) { if let ExprKind::Index(..) = &expr.kind { From aff0e6d6d3aec4ce29af95a4dcf615650f1c5e4d Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 09:24:54 +0700 Subject: [PATCH 002/107] add uitest for indexing in unnecessary const items --- tests/ui/unnecessary_operation.fixed | 13 ++++++++++++- tests/ui/unnecessary_operation.rs | 13 ++++++++++++- tests/ui/unnecessary_operation.stderr | 20 +++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/tests/ui/unnecessary_operation.fixed b/tests/ui/unnecessary_operation.fixed index 11761c6c90e36..e54491ab02350 100644 --- a/tests/ui/unnecessary_operation.fixed +++ b/tests/ui/unnecessary_operation.fixed @@ -43,7 +43,7 @@ fn get_number() -> i32 { 0 } -fn get_usize() -> usize { +const fn get_usize() -> usize { 0 } fn get_struct() -> Struct { @@ -113,4 +113,15 @@ fn main() { 'label: { break 'label }; + let () = const { + assert!([42, 55].len() > get_usize()); + }; +} + +const _: () = { + assert!([42, 55].len() > get_usize()); +}; + +const fn foo() { + assert!([42, 55].len() > get_usize()); } diff --git a/tests/ui/unnecessary_operation.rs b/tests/ui/unnecessary_operation.rs index de0081289ac3d..679d303f3ad99 100644 --- a/tests/ui/unnecessary_operation.rs +++ b/tests/ui/unnecessary_operation.rs @@ -43,7 +43,7 @@ fn get_number() -> i32 { 0 } -fn get_usize() -> usize { +const fn get_usize() -> usize { 0 } fn get_struct() -> Struct { @@ -117,4 +117,15 @@ fn main() { 'label: { break 'label }; + let () = const { + [42, 55][get_usize()]; + }; +} + +const _: () = { + [42, 55][get_usize()]; +}; + +const fn foo() { + [42, 55][get_usize()]; } diff --git a/tests/ui/unnecessary_operation.stderr b/tests/ui/unnecessary_operation.stderr index 27be5e6f4b922..3c031d006d426 100644 --- a/tests/ui/unnecessary_operation.stderr +++ b/tests/ui/unnecessary_operation.stderr @@ -119,5 +119,23 @@ LL | | s: String::from("blah"), LL | | }; | |______^ help: statement can be reduced to: `String::from("blah");` -error: aborting due to 19 previous errors +error: unnecessary operation + --> tests/ui/unnecessary_operation.rs:121:9 + | +LL | [42, 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` + +error: unnecessary operation + --> tests/ui/unnecessary_operation.rs:126:5 + | +LL | [42, 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` + +error: unnecessary operation + --> tests/ui/unnecessary_operation.rs:130:5 + | +LL | [42, 55][get_usize()]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` + +error: aborting due to 22 previous errors From b161dc659c87df29cc9738a49105124ab30638e8 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 09:54:42 +0700 Subject: [PATCH 003/107] add utils is_inside_always_const_context --- clippy_lints/src/default_numeric_fallback.rs | 2 ++ clippy_utils/src/lib.rs | 31 ++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index fbc4ede37b1c4..51252f59db47d 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -50,6 +50,8 @@ declare_lint_pass!(DefaultNumericFallback => [DEFAULT_NUMERIC_FALLBACK]); impl<'tcx> LateLintPass<'tcx> for DefaultNumericFallback { fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) { let hir = cx.tcx.hir(); + // NOTE: this is different from `clippy_utils::is_inside_always_const_context`. + // Inline const supports type inference. let is_parent_const = matches!( hir.body_const_context(hir.body_owner_def_id(body.id())), Some(ConstContext::Const { inline: false } | ConstContext::Static(_)) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index bd0911d5268f4..8185a523f1ffa 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -100,10 +100,10 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet}; use rustc_hir::intravisit::{walk_expr, FnKind, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::{ - self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, Destination, Expr, - ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, ImplItemRef, Item, - ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, PathSegment, - PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, + self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstContext, + Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, + ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, + PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, }; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, Level, Lint, LintContext}; @@ -208,7 +208,10 @@ pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool { false } -/// Returns `true` if the given `NodeId` is inside a constant context +/// Returns `true` if the given `HirId` is inside a constant context. +/// +/// This is the same as `is_inside_always_const_context`, but also includes +/// `const fn`. /// /// # Example /// @@ -221,6 +224,24 @@ pub fn in_constant(cx: &LateContext<'_>, id: HirId) -> bool { cx.tcx.hir().is_inside_const_context(id) } +/// Returns `true` if the given `HirId` is inside an always constant context. +/// +/// This context includes: +/// * const/static items +/// * const blocks (or inline consts) +/// * associated constants +pub fn is_inside_always_const_context(tcx: TyCtxt<'_>, hir_id: HirId) -> bool { + use ConstContext::{Const, ConstFn, Static}; + let hir = tcx.hir(); + let Some(ctx) = hir.body_const_context(hir.enclosing_body_owner(hir_id)) else { + return false; + }; + match ctx { + ConstFn => false, + Static(_) | Const { inline: _ } => true, + } +} + /// Checks if a `Res` refers to a constructor of a `LangItem` /// For example, use this to check whether a function call or a pattern is `Some(..)`. pub fn is_res_lang_ctor(cx: &LateContext<'_>, res: Res, lang_item: LangItem) -> bool { From 2c61b4557688aca993f59d2170673e86c4df9101 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 03:25:58 +0000 Subject: [PATCH 004/107] do not lint on indexing inside const contexts --- clippy_lints/src/no_effect.rs | 7 ++++++- tests/ui/unnecessary_operation.fixed | 4 ++-- tests/ui/unnecessary_operation.stderr | 14 +------------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index dc017fa66348b..139c33d3f4a42 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -1,7 +1,9 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::has_drop; -use clippy_utils::{any_parent_is_automatically_derived, is_lint_allowed, path_to_local, peel_blocks}; +use clippy_utils::{ + any_parent_is_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks, +}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{ @@ -265,6 +267,9 @@ fn check_unnecessary_operation(cx: &LateContext<'_>, stmt: &Stmt<'_>) { && reduced.iter().all(|e| e.span.ctxt() == ctxt) { if let ExprKind::Index(..) = &expr.kind { + if is_inside_always_const_context(cx.tcx, expr.hir_id) { + return; + } let snippet = if let (Some(arr), Some(func)) = (snippet_opt(cx, reduced[0].span), snippet_opt(cx, reduced[1].span)) { format!("assert!({}.len() > {});", &arr, &func) diff --git a/tests/ui/unnecessary_operation.fixed b/tests/ui/unnecessary_operation.fixed index e54491ab02350..5a3c49626a1c2 100644 --- a/tests/ui/unnecessary_operation.fixed +++ b/tests/ui/unnecessary_operation.fixed @@ -114,12 +114,12 @@ fn main() { break 'label }; let () = const { - assert!([42, 55].len() > get_usize()); + [42, 55][get_usize()]; }; } const _: () = { - assert!([42, 55].len() > get_usize()); + [42, 55][get_usize()]; }; const fn foo() { diff --git a/tests/ui/unnecessary_operation.stderr b/tests/ui/unnecessary_operation.stderr index 3c031d006d426..036a9a44bbad0 100644 --- a/tests/ui/unnecessary_operation.stderr +++ b/tests/ui/unnecessary_operation.stderr @@ -119,23 +119,11 @@ LL | | s: String::from("blah"), LL | | }; | |______^ help: statement can be reduced to: `String::from("blah");` -error: unnecessary operation - --> tests/ui/unnecessary_operation.rs:121:9 - | -LL | [42, 55][get_usize()]; - | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` - -error: unnecessary operation - --> tests/ui/unnecessary_operation.rs:126:5 - | -LL | [42, 55][get_usize()]; - | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` - error: unnecessary operation --> tests/ui/unnecessary_operation.rs:130:5 | LL | [42, 55][get_usize()]; | ^^^^^^^^^^^^^^^^^^^^^^ help: statement can be written as: `assert!([42, 55].len() > get_usize());` -error: aborting due to 22 previous errors +error: aborting due to 20 previous errors From e18b27aa2a37fb7597c16f72a55f26bde6badf51 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 10:37:19 +0700 Subject: [PATCH 005/107] add uitest for issue 12816 --- tests/ui/assertions_on_constants.rs | 6 ++++++ tests/ui/assertions_on_constants.stderr | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index 1309ae45d0ae5..2e0fee08a5958 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -53,3 +53,9 @@ fn main() { const N: usize = 1024; const _: () = assert!(N.is_power_of_two()); } + +#[allow(clippy::eq_op)] +const _: () = { + assert!(true); + assert!(8 == (7 + 1)); +}; diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 00f117c949206..3a64f1892ad91 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -80,5 +80,21 @@ LL | const _: () = assert!(true); | = help: remove it -error: aborting due to 10 previous errors +error: `assert!(true)` will be optimized out by the compiler + --> tests/ui/assertions_on_constants.rs:59:5 + | +LL | assert!(true); + | ^^^^^^^^^^^^^ + | + = help: remove it + +error: `assert!(true)` will be optimized out by the compiler + --> tests/ui/assertions_on_constants.rs:60:5 + | +LL | assert!(8 == (7 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + +error: aborting due to 12 previous errors From ac600282a0cc35bd99b3e3152a655991d3c1dfea Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Fri, 24 May 2024 10:51:15 +0700 Subject: [PATCH 006/107] ignore `assertions-on-constants` in const contexts --- clippy_lints/src/assertions_on_constants.rs | 5 ++++ tests/ui/assertions_on_constants.rs | 1 - tests/ui/assertions_on_constants.stderr | 26 +-------------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 2003dd1fb0e2c..863c25aba8617 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,5 +1,6 @@ use clippy_utils::consts::{constant_with_source, Constant, ConstantSource}; use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_inside_always_const_context; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; use rustc_hir::{Expr, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; @@ -31,6 +32,10 @@ declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if is_inside_always_const_context(cx.tcx, e.hir_id) { + return; + } + let Some(macro_call) = root_macro_call_first_node(cx, e) else { return; }; diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index 2e0fee08a5958..c3574b0b466c5 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -47,7 +47,6 @@ fn main() { assert!(!CFG_FLAG); const _: () = assert!(true); - //~^ ERROR: `assert!(true)` will be optimized out by the compiler // Don't lint if the value is dependent on a defined constant: const N: usize = 1024; diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index 3a64f1892ad91..aa4868af5a8c5 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -72,29 +72,5 @@ LL | debug_assert!(true); | = help: remove it -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:49:19 - | -LL | const _: () = assert!(true); - | ^^^^^^^^^^^^^ - | - = help: remove it - -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:59:5 - | -LL | assert!(true); - | ^^^^^^^^^^^^^ - | - = help: remove it - -error: `assert!(true)` will be optimized out by the compiler - --> tests/ui/assertions_on_constants.rs:60:5 - | -LL | assert!(8 == (7 + 1)); - | ^^^^^^^^^^^^^^^^^^^^^ - | - = help: remove it - -error: aborting due to 12 previous errors +error: aborting due to 9 previous errors From a0234b4e8b8c58a8726ec64828c7144c910faf67 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Sat, 8 Jun 2024 09:39:17 +0000 Subject: [PATCH 007/107] Ignore non ExprKind::{Path,Lit) inside const context - remove now dead code in ASSERTIONS_ON_CONSTANTS cc #11966 - Partially revert "ignore `assertions-on-constants` in const contexts" This reverts commit c7074de420a2192fb40d3f2194a20dd0d1b65cc6. --- clippy_lints/src/assertions_on_constants.rs | 23 +++++++----------- tests/ui/assertions_on_constants.rs | 8 +++++-- tests/ui/assertions_on_constants.stderr | 26 ++++++++++++++++++++- tests/ui/unnecessary_operation.fixed | 1 + tests/ui/unnecessary_operation.rs | 1 + 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index 863c25aba8617..ed4cdce8cb886 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -1,8 +1,8 @@ -use clippy_utils::consts::{constant_with_source, Constant, ConstantSource}; +use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::is_inside_always_const_context; use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; -use rustc_hir::{Expr, Item, ItemKind, Node}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; @@ -32,10 +32,6 @@ declare_lint_pass!(AssertionsOnConstants => [ASSERTIONS_ON_CONSTANTS]); impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if is_inside_always_const_context(cx.tcx, e.hir_id) { - return; - } - let Some(macro_call) = root_macro_call_first_node(cx, e) else { return; }; @@ -47,17 +43,16 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnConstants { let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) else { return; }; - let Some((Constant::Bool(val), source)) = constant_with_source(cx, cx.typeck_results(), condition) else { + let Some(Constant::Bool(val)) = constant(cx, cx.typeck_results(), condition) else { return; }; - if let ConstantSource::Constant = source - && let Node::Item(Item { - kind: ItemKind::Const(..), - .. - }) = cx.tcx.parent_hir_node(e.hir_id) - { - return; + + match condition.kind { + ExprKind::Path(..) | ExprKind::Lit(_) => {}, + _ if is_inside_always_const_context(cx.tcx, e.hir_id) => return, + _ => {}, } + if val { span_lint_and_help( cx, diff --git a/tests/ui/assertions_on_constants.rs b/tests/ui/assertions_on_constants.rs index c3574b0b466c5..957154e60dece 100644 --- a/tests/ui/assertions_on_constants.rs +++ b/tests/ui/assertions_on_constants.rs @@ -1,4 +1,4 @@ -#![allow(non_fmt_panics, clippy::needless_bool)] +#![allow(non_fmt_panics, clippy::needless_bool, clippy::eq_op)] macro_rules! assert_const { ($len:expr) => { @@ -47,14 +47,18 @@ fn main() { assert!(!CFG_FLAG); const _: () = assert!(true); + //~^ ERROR: `assert!(true)` will be optimized out by the compiler + + assert!(8 == (7 + 1)); + //~^ ERROR: `assert!(true)` will be optimized out by the compiler // Don't lint if the value is dependent on a defined constant: const N: usize = 1024; const _: () = assert!(N.is_power_of_two()); } -#[allow(clippy::eq_op)] const _: () = { assert!(true); + //~^ ERROR: `assert!(true)` will be optimized out by the compiler assert!(8 == (7 + 1)); }; diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index aa4868af5a8c5..e164a999c43ee 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -72,5 +72,29 @@ LL | debug_assert!(true); | = help: remove it -error: aborting due to 9 previous errors +error: `assert!(true)` will be optimized out by the compiler + --> tests/ui/assertions_on_constants.rs:49:19 + | +LL | const _: () = assert!(true); + | ^^^^^^^^^^^^^ + | + = help: remove it + +error: `assert!(true)` will be optimized out by the compiler + --> tests/ui/assertions_on_constants.rs:52:5 + | +LL | assert!(8 == (7 + 1)); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: remove it + +error: `assert!(true)` will be optimized out by the compiler + --> tests/ui/assertions_on_constants.rs:61:5 + | +LL | assert!(true); + | ^^^^^^^^^^^^^ + | + = help: remove it + +error: aborting due to 12 previous errors diff --git a/tests/ui/unnecessary_operation.fixed b/tests/ui/unnecessary_operation.fixed index 5a3c49626a1c2..006f123cbcdfc 100644 --- a/tests/ui/unnecessary_operation.fixed +++ b/tests/ui/unnecessary_operation.fixed @@ -124,4 +124,5 @@ const _: () = { const fn foo() { assert!([42, 55].len() > get_usize()); + //~^ ERROR: unnecessary operation } diff --git a/tests/ui/unnecessary_operation.rs b/tests/ui/unnecessary_operation.rs index 679d303f3ad99..b4067c7407415 100644 --- a/tests/ui/unnecessary_operation.rs +++ b/tests/ui/unnecessary_operation.rs @@ -128,4 +128,5 @@ const _: () = { const fn foo() { [42, 55][get_usize()]; + //~^ ERROR: unnecessary operation } From ed9ccf66e9b61406176d57499e91c3bf773e95da Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:09:31 +0200 Subject: [PATCH 008/107] [`unnecessary_to_owned`]: catch to_owned from byte slice to string --- .../src/methods/unnecessary_to_owned.rs | 70 ++++++++++++++++++- clippy_utils/src/paths.rs | 1 + tests/ui/unnecessary_to_owned.fixed | 19 +++++ tests/ui/unnecessary_to_owned.rs | 19 +++++ tests/ui/unnecessary_to_owned.stderr | 60 ++++++++++++---- 5 files changed, 154 insertions(+), 15 deletions(-) diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index ae9aa83efd685..5d899415d7728 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -1,13 +1,15 @@ use super::implicit_clone::is_clone_like; use super::unnecessary_iter_cloned::{self, is_into_iter}; use clippy_config::msrvs::{self, Msrv}; -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_opt; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::source::{snippet, snippet_opt}; use clippy_utils::ty::{ get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, is_type_lang_item, peel_mid_ty_refs, }; use clippy_utils::visitors::find_all_ret_expressions; -use clippy_utils::{fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty}; +use clippy_utils::{ + fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, match_def_path, paths, return_ty, +}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::DefId; @@ -52,6 +54,9 @@ pub fn check<'tcx>( if check_into_iter_call_arg(cx, expr, method_name, receiver, msrv) { return; } + if check_string_from_utf8(cx, expr, receiver) { + return; + } check_other_call_arg(cx, expr, method_name, receiver); } } else { @@ -240,6 +245,65 @@ fn check_into_iter_call_arg( false } +/// Checks for `&String::from_utf8(bytes.{to_vec,to_owned,...}()).unwrap()` coercing to `&str`, +/// which can be written as just `std::str::from_utf8(bytes).unwrap()`. +fn check_string_from_utf8<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, receiver: &'tcx Expr<'tcx>) -> bool { + if let Some((call, arg)) = skip_addr_of_ancestors(cx, expr) + && !arg.span.from_expansion() + && let ExprKind::Call(callee, _) = call.kind + && fn_def_id(cx, call).is_some_and(|did| match_def_path(cx, did, &paths::STRING_FROM_UTF8)) + && let Some(unwrap_call) = get_parent_expr(cx, call) + && let ExprKind::MethodCall(unwrap_method_name, ..) = unwrap_call.kind + && matches!(unwrap_method_name.ident.name, sym::unwrap | sym::expect) + && let Some(ref_string) = get_parent_expr(cx, unwrap_call) + && let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) = ref_string.kind + && let adjusted_ty = cx.typeck_results().expr_ty_adjusted(ref_string) + // `&...` creates a `&String`, so only actually lint if this coerces to a `&str` + && matches!(adjusted_ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) + { + span_lint_and_then( + cx, + UNNECESSARY_TO_OWNED, + ref_string.span, + "allocating a new `String` only to create a temporary `&str` from it", + |diag| { + let arg_suggestion = format!( + "{borrow}{recv_snippet}", + recv_snippet = snippet(cx, receiver.span.source_callsite(), ".."), + borrow = if cx.typeck_results().expr_ty(receiver).is_ref() { + "" + } else { + // If not already a reference, prefix with a borrow so that it can coerce to one + "&" + } + ); + + diag.multipart_suggestion( + "convert from `&[u8]` to `&str` directly", + vec![ + // `&String::from_utf8(bytes.to_vec()).unwrap()` + // ^^^^^^^^^^^^^^^^^ + (callee.span, "core::str::from_utf8".into()), + // `&String::from_utf8(bytes.to_vec()).unwrap()` + // ^ + ( + ref_string.span.shrink_to_lo().to(unwrap_call.span.shrink_to_lo()), + String::new(), + ), + // `&String::from_utf8(bytes.to_vec()).unwrap()` + // ^^^^^^^^^^^^^^ + (arg.span, arg_suggestion), + ], + Applicability::MachineApplicable, + ); + }, + ); + true + } else { + false + } +} + /// Checks whether `expr` is an argument in an `into_iter` call and, if so, determines whether its /// call of a `to_owned`-like function is unnecessary. fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol, receiver: &Expr<'_>) -> bool { diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 9bf068ee3cdee..bf72194f370cd 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -88,6 +88,7 @@ pub const SYMBOL_INTERN: [&str; 4] = ["rustc_span", "symbol", "Symbol", "intern" pub const SYMBOL_TO_IDENT_STRING: [&str; 4] = ["rustc_span", "symbol", "Symbol", "to_ident_string"]; pub const SYM_MODULE: [&str; 3] = ["rustc_span", "symbol", "sym"]; pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"]; +pub const STRING_FROM_UTF8: [&str; 4] = ["alloc", "string", "String", "from_utf8"]; #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates pub const TOKIO_FILE_OPTIONS: [&str; 5] = ["tokio", "fs", "file", "File", "options"]; #[expect(clippy::invalid_paths)] // internal lints do not know about all external crates diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index 1afa5ab54c461..fdcac8fb08dcf 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -157,6 +157,25 @@ fn main() { require_path(&std::path::PathBuf::from("x")); require_str(&String::from("x")); require_slice(&[String::from("x")]); + + let slice = [0u8; 1024]; + let _ref_str: &str = core::str::from_utf8(&slice).expect("not UTF-8"); + let _ref_str: &str = core::str::from_utf8(b"foo").unwrap(); + let _ref_str: &str = core::str::from_utf8(b"foo".as_slice()).unwrap(); + // Expression is of type `&String`, can't suggest `str::from_utf8` here + let _ref_string = &String::from_utf8(b"foo".to_vec()).unwrap(); + macro_rules! arg_from_macro { + () => { + b"foo".to_vec() + }; + } + macro_rules! string_from_utf8_from_macro { + () => { + &String::from_utf8(b"foo".to_vec()).unwrap() + }; + } + let _ref_str: &str = &String::from_utf8(arg_from_macro!()).unwrap(); + let _ref_str: &str = string_from_utf8_from_macro!(); } fn require_c_str(_: &CStr) {} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index aa88dde43bf66..10a9727a9a798 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -157,6 +157,25 @@ fn main() { require_path(&std::path::PathBuf::from("x").to_path_buf()); require_str(&String::from("x").to_string()); require_slice(&[String::from("x")].to_owned()); + + let slice = [0u8; 1024]; + let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8"); + let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap(); + let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap(); + // Expression is of type `&String`, can't suggest `str::from_utf8` here + let _ref_string = &String::from_utf8(b"foo".to_vec()).unwrap(); + macro_rules! arg_from_macro { + () => { + b"foo".to_vec() + }; + } + macro_rules! string_from_utf8_from_macro { + () => { + &String::from_utf8(b"foo".to_vec()).unwrap() + }; + } + let _ref_str: &str = &String::from_utf8(arg_from_macro!()).unwrap(); + let _ref_str: &str = string_from_utf8_from_macro!(); } fn require_c_str(_: &CStr) {} diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 2829f3cd6e980..511b4ae119f8a 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -477,8 +477,44 @@ error: unnecessary use of `to_owned` LL | let _ = IntoIterator::into_iter([std::path::PathBuf::new()][..].to_owned()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `[std::path::PathBuf::new()][..].iter().cloned()` +error: allocating a new `String` only to create a temporary `&str` from it + --> tests/ui/unnecessary_to_owned.rs:162:26 + | +LL | let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: convert from `&[u8]` to `&str` directly + | +LL - let _ref_str: &str = &String::from_utf8(slice.to_vec()).expect("not UTF-8"); +LL + let _ref_str: &str = core::str::from_utf8(&slice).expect("not UTF-8"); + | + +error: allocating a new `String` only to create a temporary `&str` from it + --> tests/ui/unnecessary_to_owned.rs:163:26 + | +LL | let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: convert from `&[u8]` to `&str` directly + | +LL - let _ref_str: &str = &String::from_utf8(b"foo".to_vec()).unwrap(); +LL + let _ref_str: &str = core::str::from_utf8(b"foo").unwrap(); + | + +error: allocating a new `String` only to create a temporary `&str` from it + --> tests/ui/unnecessary_to_owned.rs:164:26 + | +LL | let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: convert from `&[u8]` to `&str` directly + | +LL - let _ref_str: &str = &String::from_utf8(b"foo".as_slice().to_owned()).unwrap(); +LL + let _ref_str: &str = core::str::from_utf8(b"foo".as_slice()).unwrap(); + | + error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:202:14 + --> tests/ui/unnecessary_to_owned.rs:221:14 | LL | for t in file_types.to_vec() { | ^^^^^^^^^^^^^^^^^^^ @@ -494,64 +530,64 @@ LL + let path = match get_file_path(t) { | error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:225:14 + --> tests/ui/unnecessary_to_owned.rs:244:14 | LL | let _ = &["x"][..].to_vec().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().cloned()` error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:230:14 + --> tests/ui/unnecessary_to_owned.rs:249:14 | LL | let _ = &["x"][..].to_vec().into_iter(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `["x"][..].iter().copied()` error: unnecessary use of `to_string` - --> tests/ui/unnecessary_to_owned.rs:278:24 + --> tests/ui/unnecessary_to_owned.rs:297:24 | LL | Box::new(build(y.to_string())) | ^^^^^^^^^^^^^ help: use: `y` error: unnecessary use of `to_string` - --> tests/ui/unnecessary_to_owned.rs:387:12 + --> tests/ui/unnecessary_to_owned.rs:406:12 | LL | id("abc".to_string()) | ^^^^^^^^^^^^^^^^^ help: use: `"abc"` error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:530:37 + --> tests/ui/unnecessary_to_owned.rs:549:37 | LL | IntoFuture::into_future(foo([].to_vec(), &0)); | ^^^^^^^^^^^ help: use: `[]` error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:540:18 + --> tests/ui/unnecessary_to_owned.rs:559:18 | LL | s.remove(&a.to_vec()); | ^^^^^^^^^^^ help: replace it with: `a` error: unnecessary use of `to_owned` - --> tests/ui/unnecessary_to_owned.rs:544:14 + --> tests/ui/unnecessary_to_owned.rs:563:14 | LL | s.remove(&"b".to_owned()); | ^^^^^^^^^^^^^^^ help: replace it with: `"b"` error: unnecessary use of `to_string` - --> tests/ui/unnecessary_to_owned.rs:545:14 + --> tests/ui/unnecessary_to_owned.rs:564:14 | LL | s.remove(&"b".to_string()); | ^^^^^^^^^^^^^^^^ help: replace it with: `"b"` error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:550:14 + --> tests/ui/unnecessary_to_owned.rs:569:14 | LL | s.remove(&["b"].to_vec()); | ^^^^^^^^^^^^^^^ help: replace it with: `["b"].as_slice()` error: unnecessary use of `to_vec` - --> tests/ui/unnecessary_to_owned.rs:551:14 + --> tests/ui/unnecessary_to_owned.rs:570:14 | LL | s.remove(&(&["b"]).to_vec()); | ^^^^^^^^^^^^^^^^^^ help: replace it with: `(&["b"]).as_slice()` -error: aborting due to 85 previous errors +error: aborting due to 88 previous errors From dcb6a54b80543ff0021d1b79e3c51d79b65ca468 Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Fri, 21 Jun 2024 12:03:40 +0800 Subject: [PATCH 009/107] fix wrong msrv import in `new_lint` template --- clippy_dev/src/new_lint.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 2e56eb8ec15f8..d762e30ef02e1 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -273,7 +273,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { result.push_str(&if enable_msrv { formatdoc!( r#" - use clippy_utils::msrvs::{{self, Msrv}}; + use clippy_config::msrvs::{{self, Msrv}}; {pass_import} use rustc_lint::{{{context_import}, {pass_type}, LintContext}}; use rustc_session::impl_lint_pass; @@ -399,7 +399,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R let _: fmt::Result = writedoc!( lint_file_contents, r#" - use clippy_utils::msrvs::{{self, Msrv}}; + use clippy_config::msrvs::{{self, Msrv}}; use rustc_lint::{{{context_import}, LintContext}}; use super::{name_upper}; From 625091d2368e3f0f6d0127b27b183d5d855c4d08 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Sun, 23 Jun 2024 12:21:10 +0100 Subject: [PATCH 010/107] Fix doc nits --- clippy_lints/src/await_holding_invalid.rs | 46 +++---- clippy_lints/src/casts/mod.rs | 113 +++++++++--------- clippy_lints/src/endian_bytes.rs | 4 +- clippy_lints/src/methods/mod.rs | 8 +- clippy_lints/src/same_name_method.rs | 2 +- .../await_holding_invalid_type.stderr | 6 +- tests/ui/await_holding_lock.rs | 26 ++-- tests/ui/await_holding_lock.stderr | 78 ++++++------ tests/ui/await_holding_refcell_ref.rs | 12 +- tests/ui/await_holding_refcell_ref.stderr | 24 ++-- 10 files changed, 163 insertions(+), 156 deletions(-) diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index f25a474d9bbd6..dac1f4ed404a1 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -11,21 +11,25 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Checks for calls to await while holding a non-async-aware MutexGuard. + /// Checks for calls to `await` while holding a non-async-aware + /// `MutexGuard`. /// /// ### Why is this bad? - /// The Mutex types found in std::sync and parking_lot - /// are not designed to operate in an async context across await points. + /// The Mutex types found in [`std::sync`] and + /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are + /// not designed to operate in an async context across await points. /// - /// There are two potential solutions. One is to use an async-aware Mutex - /// type. Many asynchronous foundation crates provide such a Mutex type. The - /// other solution is to ensure the mutex is unlocked before calling await, - /// either by introducing a scope or an explicit call to Drop::drop. + /// There are two potential solutions. One is to use an async-aware `Mutex` + /// type. Many asynchronous foundation crates provide such a `Mutex` type. + /// The other solution is to ensure the mutex is unlocked before calling + /// `await`, either by introducing a scope or an explicit call to + /// [`Drop::drop`]. /// /// ### Known problems /// Will report false positive for explicitly dropped guards - /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is - /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard. + /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A + /// workaround for this is to wrap the `.lock()` call in a block instead of + /// explicitly dropping the guard. /// /// ### Example /// ```no_run @@ -73,11 +77,11 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`. + /// Checks for calls to `await` while holding a `RefCell`, `Ref`, or `RefMut`. /// /// ### Why is this bad? /// `RefCell` refs only check for exclusive mutable access - /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point + /// at runtime. Holding a `RefCell` ref across an await suspension point /// risks panics from a mutable ref shared while other refs are outstanding. /// /// ### Known problems @@ -131,13 +135,13 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Allows users to configure types which should not be held across `await` + /// Allows users to configure types which should not be held across await /// suspension points. /// /// ### Why is this bad? - /// There are some types which are perfectly "safe" to be used concurrently - /// from a memory access perspective but will cause bugs at runtime if they - /// are held in such a way. + /// There are some types which are perfectly safe to use concurrently from + /// a memory access perspective, but that will cause bugs at runtime if + /// they are held in such a way. /// /// ### Example /// @@ -228,15 +232,15 @@ impl AwaitHolding { cx, AWAIT_HOLDING_LOCK, ty_cause.source_info.span, - "this `MutexGuard` is held across an `await` point", + "this `MutexGuard` is held across an await point", |diag| { diag.help( "consider using an async-aware `Mutex` type or ensuring the \ - `MutexGuard` is dropped before calling await", + `MutexGuard` is dropped before calling `await`", ); diag.span_note( await_points(), - "these are all the `await` points this lock is held through", + "these are all the await points this lock is held through", ); }, ); @@ -245,12 +249,12 @@ impl AwaitHolding { cx, AWAIT_HOLDING_REFCELL_REF, ty_cause.source_info.span, - "this `RefCell` reference is held across an `await` point", + "this `RefCell` reference is held across an await point", |diag| { diag.help("ensure the reference is dropped before calling `await`"); diag.span_note( await_points(), - "these are all the `await` points this reference is held through", + "these are all the await points this reference is held through", ); }, ); @@ -268,7 +272,7 @@ fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPa AWAIT_HOLDING_INVALID_TYPE, span, format!( - "`{}` may not be held across an `await` point per `clippy.toml`", + "`{}` may not be held across an await point per `clippy.toml`", disallowed.path() ), |diag| { diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index e60c36ced75da..d8bae3b48bf4f 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -32,7 +32,7 @@ use rustc_session::impl_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for casts from any numerical to a float type where + /// Checks for casts from any numeric type to a float type where /// the receiving type cannot store all values from the original type without /// rounding errors. This possible rounding is to be expected, so this lint is /// `Allow` by default. @@ -58,14 +58,14 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts from a signed to an unsigned numerical + /// Checks for casts from a signed to an unsigned numeric /// type. In this case, negative values wrap around to large positive values, - /// which can be quite surprising in practice. However, as the cast works as + /// which can be quite surprising in practice. However, since the cast works as /// defined, this lint is `Allow` by default. /// /// ### Why is this bad? /// Possibly surprising results. You can activate this lint - /// as a one-time check to see where numerical wrapping can arise. + /// as a one-time check to see where numeric wrapping can arise. /// /// ### Example /// ```no_run @@ -80,7 +80,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts between numerical types that may + /// Checks for casts between numeric types that may /// truncate large values. This is expected behavior, so the cast is `Allow` by /// default. It suggests user either explicitly ignore the lint, /// or use `try_from()` and handle the truncation, default, or panic explicitly. @@ -120,17 +120,16 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for casts from an unsigned type to a signed type of - /// the same size, or possibly smaller due to target dependent integers. - /// Performing such a cast is a 'no-op' for the compiler, i.e., nothing is - /// changed at the bit level, and the binary representation of the value is + /// the same size, or possibly smaller due to target-dependent integers. + /// Performing such a cast is a no-op for the compiler (that is, nothing is + /// changed at the bit level), and the binary representation of the value is /// reinterpreted. This can cause wrapping if the value is too big /// for the target signed type. However, the cast works as defined, so this lint /// is `Allow` by default. /// /// ### Why is this bad? /// While such a cast is not bad in itself, the results can - /// be surprising when this is not the intended behavior, as demonstrated by the - /// example below. + /// be surprising when this is not the intended behavior: /// /// ### Example /// ```no_run @@ -144,16 +143,16 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts between numerical types that may - /// be replaced by safe conversion functions. + /// Checks for casts between numeric types that can be replaced by safe + /// conversion functions. /// /// ### Why is this bad? - /// Rust's `as` keyword will perform many kinds of - /// conversions, including silently lossy conversions. Conversion functions such - /// as `i32::from` will only perform lossless conversions. Using the conversion - /// functions prevents conversions from turning into silent lossy conversions if - /// the types of the input expressions ever change, and make it easier for - /// people reading the code to know that the conversion is lossless. + /// Rust's `as` keyword will perform many kinds of conversions, including + /// silently lossy conversions. Conversion functions such as `i32::from` + /// will only perform lossless conversions. Using the conversion functions + /// prevents conversions from becoming silently lossy if the input types + /// ever change, and makes it clear for people reading the code that the + /// conversion is lossless. /// /// ### Example /// ```no_run @@ -177,19 +176,21 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts to the same type, casts of int literals to integer types, casts of float - /// literals to float types and casts between raw pointers without changing type or constness. + /// Checks for casts to the same type, casts of int literals to integer + /// types, casts of float literals to float types, and casts between raw + /// pointers that don't change type or constness. /// /// ### Why is this bad? /// It's just unnecessary. /// /// ### Known problems - /// When the expression on the left is a function call, the lint considers the return type to be - /// a type alias if it's aliased through a `use` statement - /// (like `use std::io::Result as IoResult`). It will not lint such cases. + /// When the expression on the left is a function call, the lint considers + /// the return type to be a type alias if it's aliased through a `use` + /// statement (like `use std::io::Result as IoResult`). It will not lint + /// such cases. /// - /// This check is also rather primitive. It will only work on primitive types without any - /// intermediate references, raw pointers and trait objects may or may not work. + /// This check will only work on primitive types without any intermediate + /// references: raw pointers and trait objects may or may not work. /// /// ### Example /// ```no_run @@ -211,17 +212,17 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for casts, using `as` or `pointer::cast`, - /// from a less-strictly-aligned pointer to a more-strictly-aligned pointer + /// Checks for casts, using `as` or `pointer::cast`, from a + /// less strictly aligned pointer to a more strictly aligned pointer. /// /// ### Why is this bad? - /// Dereferencing the resulting pointer may be undefined - /// behavior. + /// Dereferencing the resulting pointer may be undefined behavior. /// /// ### Known problems - /// Using `std::ptr::read_unaligned` and `std::ptr::write_unaligned` or similar - /// on the resulting pointer is fine. Is over-zealous: Casts with manual alignment checks or casts like - /// u64-> u8 -> u16 can be fine. Miri is able to do a more in-depth analysis. + /// Using [`std::ptr::read_unaligned`] and [`std::ptr::write_unaligned`] or + /// similar on the resulting pointer is fine. Is over-zealous: casts with + /// manual alignment checks or casts like `u64` -> `u8` -> `u16` can be + /// fine. Miri is able to do a more in-depth analysis. /// /// ### Example /// ```no_run @@ -234,20 +235,21 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub CAST_PTR_ALIGNMENT, pedantic, - "cast from a pointer to a more-strictly-aligned pointer" + "cast from a pointer to a more strictly aligned pointer" } declare_clippy_lint! { /// ### What it does - /// Checks for casts of function pointers to something other than usize + /// Checks for casts of function pointers to something other than `usize`. /// /// ### Why is this bad? - /// Casting a function pointer to anything other than usize/isize is not portable across - /// architectures, because you end up losing bits if the target type is too small or end up with a - /// bunch of extra bits that waste space and add more instructions to the final binary than - /// strictly necessary for the problem + /// Casting a function pointer to anything other than `usize`/`isize` is + /// not portable across architectures. It either loses bits if the target + /// type is too small, or creates extra bits that waste space and bloat the + /// resulting binary. /// - /// Casting to isize also doesn't make sense since there are no signed addresses. + /// Casting to `isize` also doesn't make sense, since addresses are never + /// signed. /// /// ### Example /// ```no_run @@ -263,17 +265,17 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub FN_TO_NUMERIC_CAST, style, - "casting a function pointer to a numeric type other than usize" + "casting a function pointer to a numeric type other than `usize`" } declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to a numeric type not wide enough to - /// store address. + /// store an address. /// /// ### Why is this bad? /// Such a cast discards some bits of the function's address. If this is intended, it would be more - /// clearly expressed by casting to usize first, then casting the usize to the intended type (with + /// clearly expressed by casting to `usize` first, then casting the `usize` to the intended type (with /// a comment) to perform the truncation. /// /// ### Example @@ -306,7 +308,7 @@ declare_clippy_lint! { /// ### Why restrict this? /// Casting a function pointer to an integer can have surprising results and can occur /// accidentally if parentheses are omitted from a function call. If you aren't doing anything - /// low-level with function pointers then you can opt-out of casting functions to integers in + /// low-level with function pointers then you can opt out of casting functions to integers in /// order to avoid mistakes. Alternatively, you can use this lint to audit all uses of function /// pointer casts in your code. /// @@ -349,8 +351,8 @@ declare_clippy_lint! { /// ### Why is this bad? /// In general, casting values to smaller types is /// error-prone and should be avoided where possible. In the particular case of - /// converting a character literal to u8, it is easy to avoid by just using a - /// byte literal instead. As an added bonus, `b'a'` is even slightly shorter + /// converting a character literal to `u8`, it is easy to avoid by just using a + /// byte literal instead. As an added bonus, `b'a'` is also slightly shorter /// than `'a' as u8`. /// /// ### Example @@ -371,12 +373,13 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `as` casts between raw pointers without changing its mutability, - /// namely `*const T` to `*const U` and `*mut T` to `*mut U`. + /// Checks for `as` casts on a raw pointer that don't change its + /// mutability, namely `*const T` to `*const U` and `*mut T` to `*mut U`. /// /// ### Why is this bad? - /// Though `as` casts between raw pointers are not terrible, `pointer::cast` is safer because - /// it cannot accidentally change the pointer's mutability nor cast the pointer to other types like `usize`. + /// Though `as` casts between raw pointers are not terrible, + /// `pointer::cast` is safer because it cannot accidentally change the + /// pointer's mutability, nor cast the pointer to other types like `usize`. /// /// ### Example /// ```no_run @@ -395,12 +398,12 @@ declare_clippy_lint! { #[clippy::version = "1.51.0"] pub PTR_AS_PTR, pedantic, - "casting using `as` from and to raw pointers that doesn't change its mutability, where `pointer::cast` could take the place of `as`" + "casting using `as` on a raw pointer that doesn't change its mutability, where `pointer::cast` could take the place of `as`" } declare_clippy_lint! { /// ### What it does - /// Checks for `as` casts between raw pointers which change its constness, namely `*const T` to + /// Checks for `as` casts on a raw pointer that change its constness, namely `*const T` to /// `*mut T` and `*mut T` to `*const T`. /// /// ### Why is this bad? @@ -423,12 +426,12 @@ declare_clippy_lint! { #[clippy::version = "1.72.0"] pub PTR_CAST_CONSTNESS, pedantic, - "casting using `as` from and to raw pointers to change constness when specialized methods apply" + "casting using `as` on raw pointers to change constness when specialized methods apply" } declare_clippy_lint! { /// ### What it does - /// Checks for casts from an enum type to an integral type which will definitely truncate the + /// Checks for casts from an enum type to an integral type that will definitely truncate the /// value. /// /// ### Why is this bad? @@ -442,7 +445,7 @@ declare_clippy_lint! { #[clippy::version = "1.61.0"] pub CAST_ENUM_TRUNCATION, suspicious, - "casts from an enum type to an integral type which will truncate the value" + "casts from an enum type to an integral type that will truncate the value" } declare_clippy_lint! { @@ -621,7 +624,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer + /// Checks for the result of a `&self`-taking `as_ptr` being cast to a mutable pointer. /// /// ### Why is this bad? /// Since `as_ptr` takes a `&self`, the pointer won't have write permissions unless interior diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index bb766e9633872..99328e3e643f8 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -33,7 +33,7 @@ declare_clippy_lint! { /// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`. /// /// ### Why restrict this? - /// To ensure use of big endian or the target’s endianness rather than little endian. + /// To ensure use of big-endian or the target’s endianness rather than little-endian. /// /// ### Example /// ```rust,ignore @@ -51,7 +51,7 @@ declare_clippy_lint! { /// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`. /// /// ### Why restrict this? - /// To ensure use of little endian or the target’s endianness rather than big endian. + /// To ensure use of little-endian or the target’s endianness rather than big-endian. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 1408f45482007..b8b24fe92ee33 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -628,12 +628,12 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` or - /// `_.or_else(|x| Err(y))`. + /// Checks for usage of `_.and_then(|x| Some(y))`, `_.and_then(|x| Ok(y))` + /// or `_.or_else(|x| Err(y))`. /// /// ### Why is this bad? - /// Readability, this can be written more concisely as - /// `_.map(|x| y)` or `_.map_err(|x| y)`. + /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| + /// y)`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 8fdd19c549f5a..508f3ae6def02 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -12,7 +12,7 @@ use std::collections::{BTreeMap, BTreeSet}; declare_clippy_lint! { /// ### What it does /// It lints if a struct has two methods with the same name: - /// one from a trait, another not from trait. + /// one from a trait, another not from a trait. /// /// ### Why restrict this? /// Confusing. diff --git a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr index a74d8757e4a5f..016ee502c242f 100644 --- a/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr +++ b/tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.stderr @@ -1,4 +1,4 @@ -error: `std::string::String` may not be held across an `await` point per `clippy.toml` +error: `std::string::String` may not be held across an await point per `clippy.toml` --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:5:9 | LL | let _x = String::from("hello"); @@ -8,13 +8,13 @@ LL | let _x = String::from("hello"); = note: `-D clippy::await-holding-invalid-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::await_holding_invalid_type)]` -error: `std::net::Ipv4Addr` may not be held across an `await` point per `clippy.toml` +error: `std::net::Ipv4Addr` may not be held across an await point per `clippy.toml` --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:10:9 | LL | let x = Ipv4Addr::new(127, 0, 0, 1); | ^ -error: `std::string::String` may not be held across an `await` point per `clippy.toml` +error: `std::string::String` may not be held across an await point per `clippy.toml` --> tests/ui-toml/await_holding_invalid_type/await_holding_invalid_type.rs:33:13 | LL | let _x = String::from("hi!"); diff --git a/tests/ui/await_holding_lock.rs b/tests/ui/await_holding_lock.rs index 8e5510e6cd01c..cecf00c934fee 100644 --- a/tests/ui/await_holding_lock.rs +++ b/tests/ui/await_holding_lock.rs @@ -8,7 +8,7 @@ mod std_mutex { pub async fn bad(x: &Mutex) -> u32 { let guard = x.lock().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } @@ -24,13 +24,13 @@ mod std_mutex { pub async fn bad_rw(x: &RwLock) -> u32 { let guard = x.read().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } pub async fn bad_rw_write(x: &RwLock) -> u32 { let mut guard = x.write().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } @@ -52,7 +52,7 @@ mod std_mutex { let first = baz().await; let guard = x.lock().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point let second = baz().await; @@ -66,7 +66,7 @@ mod std_mutex { let second = { let guard = x.lock().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await }; @@ -79,7 +79,7 @@ mod std_mutex { pub fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { async move { let guard = x.lock().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } } @@ -92,7 +92,7 @@ mod parking_lot_mutex { pub async fn bad(x: &Mutex) -> u32 { let guard = x.lock(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } @@ -108,13 +108,13 @@ mod parking_lot_mutex { pub async fn bad_rw(x: &RwLock) -> u32 { let guard = x.read(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } pub async fn bad_rw_write(x: &RwLock) -> u32 { let mut guard = x.write(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } @@ -136,7 +136,7 @@ mod parking_lot_mutex { let first = baz().await; let guard = x.lock(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point let second = baz().await; @@ -150,7 +150,7 @@ mod parking_lot_mutex { let second = { let guard = x.lock(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await }; @@ -163,7 +163,7 @@ mod parking_lot_mutex { pub fn block_bad(x: &Mutex) -> impl std::future::Future + '_ { async move { let guard = x.lock(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point baz().await } } @@ -184,7 +184,7 @@ async fn no_await(x: std::sync::Mutex) { // `*guard += 1` is removed it is picked up. async fn dropped_before_await(x: std::sync::Mutex) { let mut guard = x.lock().unwrap(); - //~^ ERROR: this `MutexGuard` is held across an `await` point + //~^ ERROR: this `MutexGuard` is held across an await point *guard += 1; drop(guard); baz().await; diff --git a/tests/ui/await_holding_lock.stderr b/tests/ui/await_holding_lock.stderr index 0af48a36acca1..af61d8939483f 100644 --- a/tests/ui/await_holding_lock.stderr +++ b/tests/ui/await_holding_lock.stderr @@ -1,11 +1,11 @@ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:10:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:12:15 | LL | baz().await @@ -13,40 +13,40 @@ LL | baz().await = note: `-D clippy::await-holding-lock` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::await_holding_lock)]` -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:26:13 | LL | let guard = x.read().unwrap(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:28:15 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:32:13 | LL | let mut guard = x.write().unwrap(); | ^^^^^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:34:15 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:54:13 | LL | let guard = x.lock().unwrap(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:57:28 | LL | let second = baz().await; @@ -55,79 +55,79 @@ LL | LL | let third = baz().await; | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:68:17 | LL | let guard = x.lock().unwrap(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:70:19 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:81:17 | LL | let guard = x.lock().unwrap(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:83:19 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:94:13 | LL | let guard = x.lock(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:96:15 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:110:13 | LL | let guard = x.read(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:112:15 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:116:13 | LL | let mut guard = x.write(); | ^^^^^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:118:15 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:138:13 | LL | let guard = x.lock(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:141:28 | LL | let second = baz().await; @@ -136,40 +136,40 @@ LL | LL | let third = baz().await; | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:152:17 | LL | let guard = x.lock(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:154:19 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:165:17 | LL | let guard = x.lock(); | ^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:167:19 | LL | baz().await | ^^^^^ -error: this `MutexGuard` is held across an `await` point +error: this `MutexGuard` is held across an await point --> tests/ui/await_holding_lock.rs:186:9 | LL | let mut guard = x.lock().unwrap(); | ^^^^^^^^^ | - = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling await -note: these are all the `await` points this lock is held through + = help: consider using an async-aware `Mutex` type or ensuring the `MutexGuard` is dropped before calling `await` +note: these are all the await points this lock is held through --> tests/ui/await_holding_lock.rs:190:11 | LL | baz().await; diff --git a/tests/ui/await_holding_refcell_ref.rs b/tests/ui/await_holding_refcell_ref.rs index 5bd26c6283627..b0c92d8c1f6e6 100644 --- a/tests/ui/await_holding_refcell_ref.rs +++ b/tests/ui/await_holding_refcell_ref.rs @@ -4,13 +4,13 @@ use std::cell::RefCell; async fn bad(x: &RefCell) -> u32 { let b = x.borrow(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point baz().await } async fn bad_mut(x: &RefCell) -> u32 { let b = x.borrow_mut(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point baz().await } @@ -32,7 +32,7 @@ async fn also_bad(x: &RefCell) -> u32 { let first = baz().await; let b = x.borrow_mut(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point let second = baz().await; @@ -45,7 +45,7 @@ async fn less_bad(x: &RefCell) -> u32 { let first = baz().await; let b = x.borrow_mut(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point let second = baz().await; @@ -61,7 +61,7 @@ async fn not_good(x: &RefCell) -> u32 { let second = { let b = x.borrow_mut(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point baz().await }; @@ -74,7 +74,7 @@ async fn not_good(x: &RefCell) -> u32 { fn block_bad(x: &RefCell) -> impl std::future::Future + '_ { async move { let b = x.borrow_mut(); - //~^ ERROR: this `RefCell` reference is held across an `await` point + //~^ ERROR: this `RefCell` reference is held across an await point baz().await } } diff --git a/tests/ui/await_holding_refcell_ref.stderr b/tests/ui/await_holding_refcell_ref.stderr index 6b474c27ddc18..6c7209c9ff9e7 100644 --- a/tests/ui/await_holding_refcell_ref.stderr +++ b/tests/ui/await_holding_refcell_ref.stderr @@ -1,11 +1,11 @@ -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:6:9 | LL | let b = x.borrow(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:8:11 | LL | baz().await @@ -13,27 +13,27 @@ LL | baz().await = note: `-D clippy::await-holding-refcell-ref` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::await_holding_refcell_ref)]` -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:12:9 | LL | let b = x.borrow_mut(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:14:11 | LL | baz().await | ^^^^^ -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:34:9 | LL | let b = x.borrow_mut(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:37:24 | LL | let second = baz().await; @@ -42,40 +42,40 @@ LL | LL | let third = baz().await; | ^^^^^ -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:47:9 | LL | let b = x.borrow_mut(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:50:24 | LL | let second = baz().await; | ^^^^^ -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:63:13 | LL | let b = x.borrow_mut(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:65:15 | LL | baz().await | ^^^^^ -error: this `RefCell` reference is held across an `await` point +error: this `RefCell` reference is held across an await point --> tests/ui/await_holding_refcell_ref.rs:76:13 | LL | let b = x.borrow_mut(); | ^ | = help: ensure the reference is dropped before calling `await` -note: these are all the `await` points this reference is held through +note: these are all the await points this reference is held through --> tests/ui/await_holding_refcell_ref.rs:78:15 | LL | baz().await From cfccdbb1641289e92d414b01f67c6a22b485a327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Fern=C3=A1ndez=20Serrata?= <76864299+Rudxain@users.noreply.github.com> Date: Thu, 20 Jun 2024 02:15:23 -0400 Subject: [PATCH 011/107] Clarify that `modulo_one` only applies to ints --- clippy_lints/src/operators/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 48a442705b294..52dc12fe51efd 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -627,7 +627,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for getting the remainder of a division by one or minus + /// Checks for getting the remainder of integer division by one or minus /// one. /// /// ### Why is this bad? @@ -646,7 +646,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub MODULO_ONE, correctness, - "taking a number modulo +/-1, which can either panic/overflow or always returns 0" + "taking an integer modulo +/-1, which can either panic/overflow or always returns 0" } declare_clippy_lint! { From b60a6ad7f5ea1842fc3a23dbfd759bc8cb8b1f34 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 27 Jun 2024 12:01:49 -0400 Subject: [PATCH 012/107] Make queries more explicit --- clippy_lints/src/implied_bounds_in_impls.rs | 2 +- clippy_lints/src/methods/type_id_on_box.rs | 2 +- clippy_lints/src/needless_maybe_sized.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/implied_bounds_in_impls.rs b/clippy_lints/src/implied_bounds_in_impls.rs index 170ecf896b4e1..67b48878ca513 100644 --- a/clippy_lints/src/implied_bounds_in_impls.rs +++ b/clippy_lints/src/implied_bounds_in_impls.rs @@ -246,7 +246,7 @@ fn collect_supertrait_bounds<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds && let [.., path] = poly_trait.trait_ref.path.segments && poly_trait.bound_generic_params.is_empty() && let Some(trait_def_id) = path.res.opt_def_id() - && let predicates = cx.tcx.super_predicates_of(trait_def_id).predicates + && let predicates = cx.tcx.explicit_super_predicates_of(trait_def_id).predicates // If the trait has no supertrait, there is no need to collect anything from that bound && !predicates.is_empty() { diff --git a/clippy_lints/src/methods/type_id_on_box.rs b/clippy_lints/src/methods/type_id_on_box.rs index 6f9b38fcf83cc..b62ecef0069af 100644 --- a/clippy_lints/src/methods/type_id_on_box.rs +++ b/clippy_lints/src/methods/type_id_on_box.rs @@ -24,7 +24,7 @@ fn is_subtrait_of_any(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { cx.tcx.is_diagnostic_item(sym::Any, tr.def_id) || cx .tcx - .super_predicates_of(tr.def_id) + .explicit_super_predicates_of(tr.def_id) .predicates .iter() .any(|(clause, _)| { diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index 4922c87b206c0..a1d8ec3b32ec9 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -91,7 +91,7 @@ fn path_to_sized_bound(cx: &LateContext<'_>, trait_bound: &PolyTraitRef<'_>) -> return true; } - for &(predicate, _) in cx.tcx.super_predicates_of(trait_def_id).predicates { + for &(predicate, _) in cx.tcx.explicit_super_predicates_of(trait_def_id).predicates { if let ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() && trait_predicate.polarity == PredicatePolarity::Positive && !path.contains(&trait_predicate.def_id()) From abdd057163e638da1ba8e0cae316348579fab9e9 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 27 Jun 2024 18:56:04 +0200 Subject: [PATCH 013/107] Merge commit '68a799aea9b65e2444fbecfe32217ce7d5a3604f' into clippy-subtree-update --- .github/workflows/lintcheck.yml | 118 ++++++++ CHANGELOG.md | 3 + book/src/lint_configuration.md | 3 +- book/src/usage.md | 2 +- clippy_config/src/conf.rs | 3 +- clippy_config/src/msrvs.rs | 2 +- clippy_lints/src/arc_with_non_send_sync.rs | 2 +- clippy_lints/src/assigning_clones.rs | 78 +++++- clippy_lints/src/attrs/allow_attributes.rs | 4 +- .../attrs/allow_attributes_without_reason.rs | 2 +- clippy_lints/src/attrs/mod.rs | 16 +- clippy_lints/src/casts/ref_as_ptr.rs | 4 +- clippy_lints/src/cognitive_complexity.rs | 22 +- clippy_lints/src/copies.rs | 16 +- clippy_lints/src/declared_lints.rs | 3 + clippy_lints/src/dereference.rs | 48 ++-- .../src/field_scoped_visibility_modifiers.rs | 75 +++++ clippy_lints/src/implicit_hasher.rs | 40 ++- clippy_lints/src/implicit_return.rs | 6 +- clippy_lints/src/lib.rs | 4 +- clippy_lints/src/loops/mod.rs | 6 +- clippy_lints/src/manual_unwrap_or_default.rs | 9 +- clippy_lints/src/matches/manual_unwrap_or.rs | 111 +++++--- clippy_lints/src/matches/mod.rs | 10 +- clippy_lints/src/matches/single_match.rs | 4 +- clippy_lints/src/methods/manual_inspect.rs | 238 ++++++++++++++++ clippy_lints/src/methods/mod.rs | 54 ++++ .../src/methods/unnecessary_min_or_max.rs | 90 ++++++ clippy_lints/src/missing_const_for_fn.rs | 24 +- clippy_lints/src/missing_doc.rs | 7 +- clippy_lints/src/multiple_bound_locations.rs | 8 +- .../src/needless_borrows_for_generic_args.rs | 8 +- clippy_lints/src/needless_else.rs | 16 +- clippy_lints/src/needless_for_each.rs | 6 +- clippy_lints/src/needless_if.rs | 24 +- .../src/non_octal_unix_permissions.rs | 12 +- clippy_lints/src/octal_escapes.rs | 158 ++++------- clippy_lints/src/ranges.rs | 19 +- clippy_lints/src/string_patterns.rs | 25 +- clippy_lints/src/upper_case_acronyms.rs | 102 ++++--- .../src/utils/internal_lints/invalid_paths.rs | 1 + clippy_utils/src/ast_utils.rs | 4 +- clippy_utils/src/check_proc_macro.rs | 84 +++--- clippy_utils/src/consts.rs | 21 +- clippy_utils/src/higher.rs | 63 +++-- clippy_utils/src/hir_utils.rs | 6 +- clippy_utils/src/lib.rs | 164 ++++++----- clippy_utils/src/mir/possible_borrower.rs | 2 +- clippy_utils/src/paths.rs | 1 - clippy_utils/src/source.rs | 201 ++++++++++++-- clippy_utils/src/ty.rs | 14 + lintcheck/Cargo.toml | 15 +- lintcheck/README.md | 6 +- lintcheck/src/config.rs | 46 ++- lintcheck/src/json.rs | 122 ++++++++ lintcheck/src/main.rs | 261 ++++++++++-------- lintcheck/src/popular-crates.rs | 65 ----- lintcheck/src/popular_crates.rs | 52 ++++ rust-toolchain | 2 +- tests/integration.rs | 1 + .../ui-cargo/duplicate_mod/fail/Cargo.stderr | 32 +-- tests/ui-cargo/duplicate_mod/fail/src/main.rs | 1 - tests/ui/assigning_clones.fixed | 56 ++++ tests/ui/assigning_clones.rs | 56 ++++ tests/ui/auxiliary/external_consts.rs | 1 + tests/ui/cast.rs | 1 + tests/ui/cast.stderr | 184 ++++++------ tests/ui/cast_size.32bit.stderr | 52 ++-- tests/ui/copy_iterator.rs | 1 + tests/ui/copy_iterator.stderr | 2 +- tests/ui/crashes/ice-12284.rs | 10 + tests/ui/field_scoped_visibility_modifiers.rs | 21 ++ .../field_scoped_visibility_modifiers.stderr | 28 ++ tests/ui/implicit_return.fixed | 13 + tests/ui/implicit_return.rs | 13 + tests/ui/implicit_return.stderr | 32 +-- tests/ui/manual_inspect.fixed | 174 ++++++++++++ tests/ui/manual_inspect.rs | 186 +++++++++++++ tests/ui/manual_inspect.stderr | 182 ++++++++++++ tests/ui/manual_pattern_char_comparison.fixed | 12 + tests/ui/manual_pattern_char_comparison.rs | 12 + .../ui/manual_pattern_char_comparison.stderr | 8 +- tests/ui/manual_unwrap_or.fixed | 52 ++++ tests/ui/manual_unwrap_or.rs | 60 ++++ tests/ui/manual_unwrap_or.stderr | 40 ++- tests/ui/manual_unwrap_or_default.fixed | 23 +- tests/ui/manual_unwrap_or_default.rs | 23 +- tests/ui/match_result_ok.fixed | 7 +- tests/ui/match_result_ok.rs | 7 +- tests/ui/match_result_ok.stderr | 6 +- .../missing_const_for_fn/could_be_const.fixed | 173 ++++++++++++ .../could_be_const.stderr | 84 ++++++ tests/ui/octal_escapes.rs | 27 +- tests/ui/octal_escapes.stderr | 180 ++++++------ tests/ui/option_if_let_else.fixed | 3 +- tests/ui/option_if_let_else.rs | 3 +- tests/ui/option_if_let_else.stderr | 50 ++-- tests/ui/unnecessary_min_or_max.fixed | 67 +++++ tests/ui/unnecessary_min_or_max.rs | 67 +++++ tests/ui/unnecessary_min_or_max.stderr | 107 +++++++ triagebot.toml | 3 +- util/gh-pages/index.html | 34 ++- util/gh-pages/script.js | 33 +++ 103 files changed, 3631 insertions(+), 1008 deletions(-) create mode 100644 .github/workflows/lintcheck.yml create mode 100644 clippy_lints/src/field_scoped_visibility_modifiers.rs create mode 100644 clippy_lints/src/methods/manual_inspect.rs create mode 100644 clippy_lints/src/methods/unnecessary_min_or_max.rs create mode 100644 lintcheck/src/json.rs delete mode 100644 lintcheck/src/popular-crates.rs create mode 100644 lintcheck/src/popular_crates.rs create mode 100644 tests/ui/auxiliary/external_consts.rs create mode 100644 tests/ui/crashes/ice-12284.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.rs create mode 100644 tests/ui/field_scoped_visibility_modifiers.stderr create mode 100644 tests/ui/manual_inspect.fixed create mode 100644 tests/ui/manual_inspect.rs create mode 100644 tests/ui/manual_inspect.stderr create mode 100644 tests/ui/missing_const_for_fn/could_be_const.fixed create mode 100644 tests/ui/unnecessary_min_or_max.fixed create mode 100644 tests/ui/unnecessary_min_or_max.rs create mode 100644 tests/ui/unnecessary_min_or_max.stderr diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml new file mode 100644 index 0000000000000..91c98b3a2560c --- /dev/null +++ b/.github/workflows/lintcheck.yml @@ -0,0 +1,118 @@ +name: Lintcheck + +on: pull_request + +env: + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + +concurrency: + # For a given workflow, if we push to the same PR, cancel all previous builds on that PR. + group: "${{ github.workflow }}-${{ github.event.pull_request.number}}" + cancel-in-progress: true + +jobs: + # Runs lintcheck on the PR's target branch and stores the results as an artifact + base: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + # HEAD is the generated merge commit `refs/pull/N/merge` between the PR and `master`, `HEAD^` + # being the commit from `master` that is the base of the merge + - name: Checkout base + run: git checkout HEAD^ + + # Use the lintcheck from the PR to generate the JSON in case the PR modifies lintcheck in some + # way + - name: Checkout current lintcheck + run: | + rm -rf lintcheck + git checkout ${{ github.sha }} -- lintcheck + + - name: Cache lintcheck bin + id: cache-lintcheck-bin + uses: actions/cache@v4 + with: + path: target/debug/lintcheck + key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} + + - name: Build lintcheck + if: steps.cache-lintcheck-bin.outputs.cache-hit != 'true' + run: cargo build --manifest-path=lintcheck/Cargo.toml + + - name: Create cache key + id: key + run: echo "key=lintcheck-base-${{ hashfiles('lintcheck/**') }}-$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Cache results JSON + id: cache-json + uses: actions/cache@v4 + with: + path: lintcheck-logs/lintcheck_crates_logs.json + key: ${{ steps.key.outputs.key }} + + - name: Run lintcheck + if: steps.cache-json.outputs.cache-hit != 'true' + run: ./target/debug/lintcheck --format json + + - name: Upload base JSON + uses: actions/upload-artifact@v4 + with: + name: base + path: lintcheck-logs/lintcheck_crates_logs.json + + # Runs lintcheck on the PR and stores the results as an artifact + head: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache lintcheck bin + id: cache-lintcheck-bin + uses: actions/cache@v4 + with: + path: target/debug/lintcheck + key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} + + - name: Build lintcheck + if: steps.cache-lintcheck-bin.outputs.cache-hit != 'true' + run: cargo build --manifest-path=lintcheck/Cargo.toml + + - name: Run lintcheck + run: ./target/debug/lintcheck --format json + + - name: Upload head JSON + uses: actions/upload-artifact@v4 + with: + name: head + path: lintcheck-logs/lintcheck_crates_logs.json + + # Retrieves the head and base JSON results and prints the diff to the GH actions step summary + diff: + runs-on: ubuntu-latest + + needs: [base, head] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Restore lintcheck bin + uses: actions/cache/restore@v4 + with: + path: target/debug/lintcheck + key: lintcheck-bin-${{ hashfiles('lintcheck/**') }} + fail-on-cache-miss: true + + - name: Download JSON + uses: actions/download-artifact@v4 + + - name: Diff results + run: ./target/debug/lintcheck diff {base,head}/lintcheck_crates_logs.json >> $GITHUB_STEP_SUMMARY diff --git a/CHANGELOG.md b/CHANGELOG.md index d1cd87473cef4..70ef2c7936406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5362,6 +5362,7 @@ Released 2018-09-13 [`extra_unused_type_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#extra_unused_type_parameters [`fallible_impl_from`]: https://rust-lang.github.io/rust-clippy/master/index.html#fallible_impl_from [`field_reassign_with_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_reassign_with_default +[`field_scoped_visibility_modifiers`]: https://rust-lang.github.io/rust-clippy/master/index.html#field_scoped_visibility_modifiers [`filetype_is_file`]: https://rust-lang.github.io/rust-clippy/master/index.html#filetype_is_file [`filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map [`filter_map_bool_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_bool_then @@ -5520,6 +5521,7 @@ Released 2018-09-13 [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one +[`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check [`manual_is_finite`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_finite @@ -5915,6 +5917,7 @@ Released 2018-09-13 [`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations [`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap [`unnecessary_map_on_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_map_on_constructor +[`unnecessary_min_or_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_min_or_max [`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed [`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation [`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 6dad3463aa47a..cfd34c7d2a7d1 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -454,7 +454,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** @@ -695,6 +695,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_is_ascii_check`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check) * [`manual_let_else`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else) * [`manual_non_exhaustive`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive) +* [`manual_pattern_char_comparison`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_pattern_char_comparison) * [`manual_range_contains`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains) * [`manual_rem_euclid`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid) * [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain) diff --git a/book/src/usage.md b/book/src/usage.md index 36448e4cccfa4..7a0be6994fe1b 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -36,7 +36,7 @@ You can configure lint levels on the command line by adding cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf ``` -For [CI] all warnings can be elevated to errors which will inturn fail +For [CI] all warnings can be elevated to errors which will in turn fail the build and cause Clippy to exit with a code other than `0`. ``` diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 279193e516e95..dbab3b106a80f 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -18,6 +18,7 @@ use std::{cmp, env, fmt, fs, io}; #[rustfmt::skip] const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", @@ -265,7 +266,7 @@ define_Conf! { /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), - /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON. + /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE, ITER_KV_MAP, MANUAL_C_STR_LITERALS, ASSIGNING_CLONES, LEGACY_NUMERIC_CONSTANTS, MANUAL_PATTERN_CHAR_COMPARISON, ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON. /// /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` #[default_text = ""] diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 8bdb5b317e5d9..a5761d3270cd2 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -19,7 +19,7 @@ macro_rules! msrv_aliases { msrv_aliases! { 1,81,0 { LINT_REASONS_STABILIZATION } 1,77,0 { C_STR_LITERALS } - 1,76,0 { PTR_FROM_REF } + 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } 1,70,0 { OPTION_RESULT_IS_VARIANT_AND, BINARY_HEAP_RETAIN } 1,68,0 { PATH_MAIN_SEPARATOR_STR } diff --git a/clippy_lints/src/arc_with_non_send_sync.rs b/clippy_lints/src/arc_with_non_send_sync.rs index 389338973894b..d57ab539fff42 100644 --- a/clippy_lints/src/arc_with_non_send_sync.rs +++ b/clippy_lints/src/arc_with_non_send_sync.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// `Arc` is a thread-safe `Rc` and guarantees that updates to the reference counter /// use atomic operations. To send an `Arc` across thread boundaries and /// share ownership between multiple threads, `T` must be [both `Send` and `Sync`](https://doc.rust-lang.org/std/sync/struct.Arc.html#thread-safety), - /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc` + /// so either `T` should be made `Send + Sync` or an `Rc` should be used instead of an `Arc`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index e94a6f3e3fc5c..05ea74b0d534f 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -1,16 +1,18 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::HirNode; +use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap}; use clippy_utils::sugg::Sugg; use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::mir; use rustc_middle::ty::{self, Instance, Mutability}; use rustc_session::impl_lint_pass; use rustc_span::def_id::DefId; use rustc_span::symbol::sym; -use rustc_span::{ExpnKind, SyntaxContext}; +use rustc_span::{ExpnKind, Span, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -144,6 +146,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option< }; Some(CallCandidate { + span: expr.span, target, kind, method_def_id: resolved_method.def_id(), @@ -215,6 +218,10 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC return false; }; + if clone_source_borrows_from_dest(cx, lhs, call.span) { + return false; + } + // Now take a look if the impl block defines an implementation for the method that we're interested // in. If not, then we're using a default implementation, which is not interesting, so we will // not suggest the lint. @@ -222,6 +229,74 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC implemented_fns.contains_key(&provided_fn.def_id) } +/// Checks if the data being cloned borrows from the place that is being assigned to: +/// +/// ``` +/// let mut s = String::new(); +/// let s2 = &s; +/// s = s2.to_owned(); +/// ``` +/// +/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows. +fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool { + /// If this basic block only exists to drop a local as part of an assignment, returns its + /// successor. Otherwise returns the basic block that was passed in. + fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock { + if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind { + target + } else { + bb + } + } + + let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else { + return false; + }; + let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir); + + // The operation `dest = src.to_owned()` in MIR is split up across 3 blocks *if* the type has `Drop` + // code. For types that don't, the second basic block is simply skipped. + // For the doc example above that would be roughly: + // + // bb0: + // s2 = &s + // s_temp = ToOwned::to_owned(move s2) -> bb1 + // + // bb1: + // drop(s) -> bb2 // drop the old string + // + // bb2: + // s = s_temp + for bb in mir.basic_blocks.iter() { + let terminator = bb.terminator(); + + // Look for the to_owned/clone call. + if terminator.source_info.span != call_span { + continue; + } + + if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind + && let [source] = &**args + && let mir::Operand::Move(source) = &source.node + && let assign_bb = skip_drop_block(mir, assign_bb) + // Skip any storage statements as they are just noise + && let Some(assignment) = mir.basic_blocks[assign_bb].statements + .iter() + .find(|stmt| { + !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_)) + }) + && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind + && let Some(borrowers) = borrow_map.get(&borrowed.local) + && borrowers.contains(source.local) + { + return true; + } + + return false; + } + false +} + fn suggest<'tcx>( cx: &LateContext<'tcx>, ctxt: SyntaxContext, @@ -255,6 +330,7 @@ enum TargetTrait { #[derive(Debug)] struct CallCandidate<'tcx> { + span: Span, target: TargetTrait, kind: CallKind<'tcx>, // DefId of the called method from an impl block that implements the target trait diff --git a/clippy_lints/src/attrs/allow_attributes.rs b/clippy_lints/src/attrs/allow_attributes.rs index c5b6980b0b900..df9994086cd48 100644 --- a/clippy_lints/src/attrs/allow_attributes.rs +++ b/clippy_lints/src/attrs/allow_attributes.rs @@ -1,17 +1,17 @@ +use super::ALLOW_ATTRIBUTES; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::is_from_proc_macro; use rustc_ast::{AttrStyle, Attribute}; use rustc_errors::Applicability; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -use super::ALLOW_ATTRIBUTES; // Separate each crate's features. pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) { if !in_external_macro(cx.sess(), attr.span) && let AttrStyle::Outer = attr.style && let Some(ident) = attr.ident() - && !is_from_proc_macro(cx, &attr) + && !is_from_proc_macro(cx, attr) { span_lint_and_sugg( cx, diff --git a/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/clippy_lints/src/attrs/allow_attributes_without_reason.rs index 8bf985a36c7a2..4b42616a636bc 100644 --- a/clippy_lints/src/attrs/allow_attributes_without_reason.rs +++ b/clippy_lints/src/attrs/allow_attributes_without_reason.rs @@ -17,7 +17,7 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet } // Check if the attribute is in an external macro and therefore out of the developer's control - if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, &attr) { + if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, attr) { return; } diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index da19f17998aff..8ec60314cc9a0 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -1,7 +1,7 @@ //! checks for attributes -mod allow_attributes_without_reason; mod allow_attributes; +mod allow_attributes_without_reason; mod blanket_clippy_restriction_lints; mod deprecated_cfg_attr; mod deprecated_semver; @@ -505,6 +505,7 @@ pub struct Attributes { } impl_lint_pass!(Attributes => [ + ALLOW_ATTRIBUTES, ALLOW_ATTRIBUTES_WITHOUT_REASON, INLINE_ALWAYS, DEPRECATED_SEMVER, @@ -534,15 +535,12 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { if is_lint_level(ident.name, attr.id) { blanket_clippy_restriction_lints::check(cx, ident.name, items); } - if matches!(ident.name, sym::allow) { - if self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { - allow_attributes::check(cx, attr); - } + if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { + allow_attributes::check(cx, attr); } - if matches!(ident.name, sym::allow | sym::expect) { - if self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { - allow_attributes_without_reason::check(cx, ident.name, items, attr); - } + if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) + { + allow_attributes_without_reason::check(cx, ident.name, items, attr); } if items.is_empty() || !attr.has_name(sym::deprecated) { return; diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index f42bafce4dde1..5f48b8bd2063e 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -22,9 +22,9 @@ pub(super) fn check<'tcx>( if matches!(cast_from.kind(), ty::Ref(..)) && let ty::RawPtr(_, to_mutbl) = cast_to.kind() - && let Some(use_cx) = expr_use_ctxt(cx, expr) + && let use_cx = expr_use_ctxt(cx, expr) // TODO: only block the lint if `cast_expr` is a temporary - && !matches!(use_cx.node, ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) + && !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) { let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let fn_name = match to_mutbl { diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index e41abf422349d..60815f4f2afbb 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -1,7 +1,7 @@ //! calculate cognitive complexity and warn about overly complex functions use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::{IntoSpan, SpanRangeExt}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack}; @@ -12,7 +12,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; -use rustc_span::{sym, BytePos, Span}; +use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does @@ -50,7 +50,6 @@ impl CognitiveComplexity { impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); impl CognitiveComplexity { - #[expect(clippy::cast_possible_truncation)] fn check<'tcx>( &mut self, cx: &LateContext<'tcx>, @@ -100,17 +99,12 @@ impl CognitiveComplexity { FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span, FnKind::Closure => { let header_span = body_span.with_hi(decl.output.span().lo()); - let pos = snippet_opt(cx, header_span).and_then(|snip| { - let low_offset = snip.find('|')?; - let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?; - let low = header_span.lo() + BytePos(low_offset as u32); - let high = low + BytePos(high_offset as u32 + 1); - - Some((low, high)) - }); - - if let Some((low, high)) = pos { - Span::new(low, high, header_span.ctxt(), header_span.parent()) + #[expect(clippy::range_plus_one)] + if let Some(range) = header_span.map_range(cx, |src, range| { + let mut idxs = src.get(range.clone())?.match_indices('|'); + Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1) + }) { + range.with_ctxt(header_span.ctxt()) } else { return; } diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 480df675d7544..d896452be9209 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; -use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; +use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, IntoSpan, SpanRangeExt}; use clippy_utils::ty::{needs_ordered_drop, InteriorMut}; use clippy_utils::visitors::for_each_expr_without_closures; use clippy_utils::{ @@ -14,7 +14,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, Span, Symbol}; +use rustc_span::{Span, Symbol}; use std::borrow::Cow; declare_clippy_lint! { @@ -266,12 +266,12 @@ fn lint_branches_sharing_code<'tcx>( let span = span.with_hi(last_block.span.hi()); // Improve formatting if the inner block has indention (i.e. normal Rust formatting) - let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent()); - let span = if snippet_opt(cx, test_span).map_or(false, |snip| snip == " ") { - span.with_lo(test_span.lo()) - } else { - span - }; + let span = span + .map_range(cx, |src, range| { + (range.start > 4 && src.get(range.start - 4..range.start)? == " ") + .then_some(range.start - 4..range.end) + }) + .map_or(span, |range| range.with_ctxt(span.ctxt())); (span, suggestion.to_string()) }); diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 76a0e450e59aa..638de5e818c8e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -178,6 +178,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::explicit_write::EXPLICIT_WRITE_INFO, crate::extra_unused_type_parameters::EXTRA_UNUSED_TYPE_PARAMETERS_INFO, crate::fallible_impl_from::FALLIBLE_IMPL_FROM_INFO, + crate::field_scoped_visibility_modifiers::FIELD_SCOPED_VISIBILITY_MODIFIERS_INFO, crate::float_literal::EXCESSIVE_PRECISION_INFO, crate::float_literal::LOSSY_FLOAT_LITERAL_INFO, crate::floating_point_arithmetic::IMPRECISE_FLOPS_INFO, @@ -402,6 +403,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_C_STR_LITERALS_INFO, crate::methods::MANUAL_FILTER_MAP_INFO, crate::methods::MANUAL_FIND_MAP_INFO, + crate::methods::MANUAL_INSPECT_INFO, crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_NEXT_BACK_INFO, crate::methods::MANUAL_OK_OR_INFO, @@ -469,6 +471,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::methods::UNNECESSARY_JOIN_INFO, crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO, crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO, + crate::methods::UNNECESSARY_MIN_OR_MAX_INFO, crate::methods::UNNECESSARY_RESULT_MAP_OR_ELSE_INFO, crate::methods::UNNECESSARY_SORT_BY_INFO, crate::methods::UNNECESSARY_TO_OWNED_INFO, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index f451758c33507..a115f8d063140 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -6,7 +6,7 @@ use clippy_utils::{ expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, }; use core::mem; -use rustc_ast::util::parser::{PREC_UNAMBIGUOUS, PREC_PREFIX}; +use rustc_ast::util::parser::{PREC_PREFIX, PREC_UNAMBIGUOUS}; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_ty, Visitor}; @@ -260,18 +260,13 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { (None, kind) => { let expr_ty = typeck.expr_ty(expr); let use_cx = expr_use_ctxt(cx, expr); - let adjusted_ty = match &use_cx { - Some(use_cx) => match use_cx.adjustments { - [.., a] => a.target, - _ => expr_ty, - }, - _ => typeck.expr_ty_adjusted(expr), - }; + let adjusted_ty = use_cx.adjustments.last().map_or(expr_ty, |a| a.target); - match (use_cx, kind) { - (Some(use_cx), RefOp::Deref) => { + match kind { + RefOp::Deref if use_cx.same_ctxt => { + let use_node = use_cx.use_node(cx); let sub_ty = typeck.expr_ty(sub_expr); - if let ExprUseNode::FieldAccess(name) = use_cx.node + if let ExprUseNode::FieldAccess(name) = use_node && !use_cx.moved_before_use && !ty_contains_field(sub_ty, name.name) { @@ -288,9 +283,9 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { } else if sub_ty.is_ref() // Linting method receivers would require verifying that name lookup // would resolve the same way. This is complicated by trait methods. - && !use_cx.node.is_recv() - && let Some(ty) = use_cx.node.defined_ty(cx) - && TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()).is_deref_stable() + && !use_node.is_recv() + && let Some(ty) = use_node.defined_ty(cx) + && TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()).is_deref_stable() { self.state = Some(( State::ExplicitDeref { mutability: None }, @@ -301,7 +296,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { )); } }, - (_, RefOp::Method { mutbl, is_ufcs }) + RefOp::Method { mutbl, is_ufcs } if !is_lint_allowed(cx, EXPLICIT_DEREF_METHODS, expr.hir_id) // Allow explicit deref in method chains. e.g. `foo.deref().bar()` && (is_ufcs || !in_postfix_position(cx, expr)) => @@ -319,7 +314,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }, )); }, - (Some(use_cx), RefOp::AddrOf(mutability)) => { + RefOp::AddrOf(mutability) if use_cx.same_ctxt => { // Find the number of times the borrow is auto-derefed. let mut iter = use_cx.adjustments.iter(); let mut deref_count = 0usize; @@ -338,10 +333,11 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }; }; - let stability = use_cx.node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { - TyCoercionStability::for_defined_ty(cx, ty, use_cx.node.is_return()) + let use_node = use_cx.use_node(cx); + let stability = use_node.defined_ty(cx).map_or(TyCoercionStability::None, |ty| { + TyCoercionStability::for_defined_ty(cx, ty, use_node.is_return()) }); - let can_auto_borrow = match use_cx.node { + let can_auto_borrow = match use_node { ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use && matches!(sub_expr.kind, ExprKind::Field(..)) => { @@ -353,7 +349,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // deref through `ManuallyDrop<_>` will not compile. !adjust_derefs_manually_drop(use_cx.adjustments, expr_ty) }, - ExprUseNode::Callee | ExprUseNode::FieldAccess(_) => true, + ExprUseNode::Callee | ExprUseNode::FieldAccess(_) if !use_cx.moved_before_use => true, ExprUseNode::MethodArg(hir_id, _, 0) if !use_cx.moved_before_use => { // Check for calls to trait methods where the trait is implemented // on a reference. @@ -363,9 +359,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { // priority. if let Some(fn_id) = typeck.type_dependent_def_id(hir_id) && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) - && let arg_ty = cx - .tcx - .erase_regions(use_cx.adjustments.last().map_or(expr_ty, |a| a.target)) + && let arg_ty = cx.tcx.erase_regions(adjusted_ty) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() && let args = typeck.node_args_opt(hir_id).map(|args| &args[1..]).unwrap_or_default() @@ -443,7 +437,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { count: deref_count - required_refs, msg, stability, - for_field_access: if let ExprUseNode::FieldAccess(name) = use_cx.node + for_field_access: if let ExprUseNode::FieldAccess(name) = use_node && !use_cx.moved_before_use { Some(name.name) @@ -453,7 +447,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { }), StateData { first_expr: expr, - adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + adjusted_ty, }, )); } else if stability.is_deref_stable() @@ -465,12 +459,12 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { State::Borrow { mutability }, StateData { first_expr: expr, - adjusted_ty: use_cx.adjustments.last().map_or(expr_ty, |a| a.target), + adjusted_ty, }, )); } }, - (None, _) | (_, RefOp::Method { .. }) => (), + _ => {}, } }, ( diff --git a/clippy_lints/src/field_scoped_visibility_modifiers.rs b/clippy_lints/src/field_scoped_visibility_modifiers.rs new file mode 100644 index 0000000000000..bb74e345703ff --- /dev/null +++ b/clippy_lints/src/field_scoped_visibility_modifiers.rs @@ -0,0 +1,75 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_ast::ast::{Item, ItemKind, VisibilityKind}; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of scoped visibility modifiers, like `pub(crate)`, on fields. These + /// make a field visible within a scope between public and private. + /// + /// ### Why restrict this? + /// Scoped visibility modifiers cause a field to be accessible within some scope between + /// public and private, potentially within an entire crate. This allows for fields to be + /// non-private while upholding internal invariants, but can be a code smell. Scoped visibility + /// requires checking a greater area, potentially an entire crate, to verify that an invariant + /// is upheld, and global analysis requires a lot of effort. + /// + /// ### Example + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// pub(crate) first_field: bool, + /// pub(super) second_field: bool + /// } + /// } + /// ``` + /// Use instead: + /// ```no_run + /// pub mod public_module { + /// struct MyStruct { + /// first_field: bool, + /// second_field: bool + /// } + /// impl MyStruct { + /// pub(crate) fn get_first_field(&self) -> bool { + /// self.first_field + /// } + /// pub(super) fn get_second_field(&self) -> bool { + /// self.second_field + /// } + /// } + /// } + /// ``` + #[clippy::version = "1.78.0"] + pub FIELD_SCOPED_VISIBILITY_MODIFIERS, + restriction, + "checks for usage of a scoped visibility modifier, like `pub(crate)`, on fields" +} + +declare_lint_pass!(FieldScopedVisibilityModifiers => [FIELD_SCOPED_VISIBILITY_MODIFIERS]); + +impl EarlyLintPass for FieldScopedVisibilityModifiers { + fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) { + let ItemKind::Struct(ref st, _) = item.kind else { + return; + }; + for field in st.fields() { + let VisibilityKind::Restricted { path, .. } = &field.vis.kind else { + continue; + }; + if !path.segments.is_empty() && path.segments[0].ident.name == rustc_span::symbol::kw::SelfLower { + // pub(self) is equivalent to not using pub at all, so we ignore it + continue; + } + span_lint_and_help( + cx, + FIELD_SCOPED_VISIBILITY_MODIFIERS, + field.vis.span, + "scoped visibility modifier on a field", + None, + "consider making the field private and adding a scoped visibility method for it", + ); + } + } +} diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index ca830af3b2f36..344a04e6e7e82 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -14,7 +14,7 @@ use rustc_span::symbol::sym; use rustc_span::Span; use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_opt}; +use clippy_utils::source::{snippet, IntoSpan, SpanRangeExt}; use clippy_utils::ty::is_type_diagnostic_item; declare_clippy_lint! { @@ -59,10 +59,8 @@ declare_clippy_lint! { declare_lint_pass!(ImplicitHasher => [IMPLICIT_HASHER]); impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { - #[expect(clippy::cast_possible_truncation, clippy::too_many_lines)] + #[expect(clippy::too_many_lines)] fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - use rustc_span::BytePos; - fn suggestion( cx: &LateContext<'_>, diag: &mut Diag<'_, ()>, @@ -123,10 +121,11 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { } let generics_suggestion_span = impl_.generics.span.substitute_dummy({ - let pos = snippet_opt(cx, item.span.until(target.span())) - .and_then(|snip| Some(item.span.lo() + BytePos(snip.find("impl")? as u32 + 4))); - if let Some(pos) = pos { - Span::new(pos, pos, item.span.ctxt(), item.span.parent()) + let range = (item.span.lo()..target.span().lo()).map_range(cx, |src, range| { + Some(src.get(range.clone())?.find("impl")? + 4..range.end) + }); + if let Some(range) = range { + range.with_ctxt(item.span.ctxt()) } else { return; } @@ -163,21 +162,16 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitHasher { continue; } let generics_suggestion_span = generics.span.substitute_dummy({ - let pos = snippet_opt( - cx, - Span::new( - item.span.lo(), - body.params[0].pat.span.lo(), - item.span.ctxt(), - item.span.parent(), - ), - ) - .and_then(|snip| { - let i = snip.find("fn")?; - Some(item.span.lo() + BytePos((i + snip[i..].find('(')?) as u32)) - }) - .expect("failed to create span for type parameters"); - Span::new(pos, pos, item.span.ctxt(), item.span.parent()) + let range = (item.span.lo()..body.params[0].pat.span.lo()).map_range(cx, |src, range| { + let (pre, post) = src.get(range.clone())?.split_once("fn")?; + let pos = post.find('(')? + pre.len() + 2; + Some(pos..pos) + }); + if let Some(range) = range { + range.with_ctxt(item.span.ctxt()) + } else { + return; + } }); let mut ctr_vis = ImplicitHasherConstructorVisitor::new(cx, target); diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 2f543781c44c1..a102b434cfabb 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{get_async_fn_body, is_async_fn}; +use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; @@ -245,6 +245,10 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { } else { body.value }; + + if is_from_proc_macro(cx, expr) { + return; + } lint_implicit_returns(cx, expr, expr.span.ctxt(), None); } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 63461c14d7790..32fad0f02ceab 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -137,6 +137,7 @@ mod exit; mod explicit_write; mod extra_unused_type_parameters; mod fallible_impl_from; +mod field_scoped_visibility_modifiers; mod float_literal; mod floating_point_arithmetic; mod format; @@ -1168,7 +1169,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { ..Default::default() }) }); - store.register_late_pass(|_| Box::new(string_patterns::StringPatterns)); + store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); + store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 0868294215305..64ea591993dab 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -356,10 +356,10 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for loops which have a range bound that is a mutable variable + /// Checks for loops with a range bound that is a mutable variable. /// /// ### Why is this bad? - /// One might think that modifying the mutable variable changes the loop bounds + /// One might think that modifying the mutable variable changes the loop bounds. It doesn't. /// /// ### Known problems /// False positive when mutation is followed by a `break`, but the `break` is not immediately @@ -381,7 +381,7 @@ declare_clippy_lint! { /// let mut foo = 42; /// for i in 0..foo { /// foo -= 1; - /// println!("{}", i); // prints numbers from 0 to 42, not 0 to 21 + /// println!("{i}"); // prints numbers from 0 to 41, not 0 to 21 /// } /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/manual_unwrap_or_default.rs b/clippy_lints/src/manual_unwrap_or_default.rs index 17399fb2cc212..58b2ebebbf080 100644 --- a/clippy_lints/src/manual_unwrap_or_default.rs +++ b/clippy_lints/src/manual_unwrap_or_default.rs @@ -53,6 +53,7 @@ declare_lint_pass!(ManualUnwrapOrDefault => [MANUAL_UNWRAP_OR_DEFAULT]); fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option { if let PatKind::TupleStruct(QPath::Resolved(_, path), &[pat], _) = pat.kind + && let PatKind::Binding(_, pat_id, _, _) = pat.kind && let Some(def_id) = path.res.opt_def_id() // Since it comes from a pattern binding, we need to get the parent to actually match // against it. @@ -60,13 +61,7 @@ fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option { && (cx.tcx.lang_items().get(LangItem::OptionSome) == Some(def_id) || cx.tcx.lang_items().get(LangItem::ResultOk) == Some(def_id)) { - let mut bindings = Vec::new(); - pat.each_binding(|_, id, _, _| bindings.push(id)); - if let &[id] = bindings.as_slice() { - Some(id) - } else { - None - } + Some(pat_id) } else { None } diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 9edd6c9540424..0940fc3219bb6 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -3,46 +3,78 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt}; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{is_res_lang_ctor, path_to_local_id, sugg}; +use clippy_utils::{is_res_lang_ctor, path_to_local_id, peel_blocks, sugg}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::LangItem::{OptionNone, ResultErr}; -use rustc_hir::{Arm, Expr, PatKind}; +use rustc_hir::{Arm, Expr, Pat, PatKind}; use rustc_lint::LateContext; +use rustc_middle::ty::Ty; use rustc_span::sym; use super::MANUAL_UNWRAP_OR; -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) { +pub(super) fn check_match<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + scrutinee: &'tcx Expr<'_>, + arms: &'tcx [Arm<'_>], +) { let ty = cx.typeck_results().expr_ty(scrutinee); - if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) { + if let Some((or_arm, unwrap_arm)) = applicable_or_arm(cx, arms) { + check_and_lint(cx, expr, unwrap_arm.pat, scrutinee, unwrap_arm.body, or_arm.body, ty); + } +} + +pub(super) fn check_if_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + let_pat: &'tcx Pat<'_>, + let_expr: &'tcx Expr<'_>, + then_expr: &'tcx Expr<'_>, + else_expr: &'tcx Expr<'_>, +) { + let ty = cx.typeck_results().expr_ty(let_expr); + check_and_lint(cx, expr, let_pat, let_expr, then_expr, peel_blocks(else_expr), ty); +} + +fn check_and_lint<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + let_pat: &'tcx Pat<'_>, + let_expr: &'tcx Expr<'_>, + then_expr: &'tcx Expr<'_>, + else_expr: &'tcx Expr<'_>, + ty: Ty<'tcx>, +) { + if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = let_pat.kind + && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, let_pat.hir_id) + && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) + && (cx.tcx.lang_items().option_some_variant() == Some(variant_id) + || cx.tcx.lang_items().result_ok_variant() == Some(variant_id)) + && let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind + && path_to_local_id(peel_blocks(then_expr), binding_hir_id) + && cx.typeck_results().expr_adjustments(then_expr).is_empty() + && let Some(ty_name) = find_type_name(cx, ty) + && let Some(or_body_snippet) = snippet_opt(cx, else_expr.span) + && let Some(indent) = indent_of(cx, expr.span) + && constant_simple(cx, cx.typeck_results(), else_expr).is_some() + { + lint(cx, expr, let_expr, ty_name, or_body_snippet, indent); + } +} + +fn find_type_name<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'static str> { + if is_type_diagnostic_item(cx, ty, sym::Option) { Some("Option") } else if is_type_diagnostic_item(cx, ty, sym::Result) { Some("Result") } else { None - } && let Some(or_arm) = applicable_or_arm(cx, arms) - && let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span) - && let Some(indent) = indent_of(cx, expr.span) - && constant_simple(cx, cx.typeck_results(), or_arm.body).is_some() - { - let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent)); - - let mut app = Applicability::MachineApplicable; - let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par(); - span_lint_and_sugg( - cx, - MANUAL_UNWRAP_OR, - expr.span, - format!("this pattern reimplements `{ty_name}::unwrap_or`"), - "replace with", - format!("{suggestion}.unwrap_or({reindented_or_body})",), - app, - ); } } -fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> { +fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<(&'a Arm<'a>, &'a Arm<'a>)> { if arms.len() == 2 && arms.iter().all(|arm| arm.guard.is_none()) && let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind { @@ -54,18 +86,33 @@ fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&' _ => false, }) && let unwrap_arm = &arms[1 - idx] - && let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind - && let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(qpath, unwrap_arm.pat.hir_id) - && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) - && (cx.tcx.lang_items().option_some_variant() == Some(variant_id) - || cx.tcx.lang_items().result_ok_variant() == Some(variant_id)) - && let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind - && path_to_local_id(unwrap_arm.body, binding_hir_id) - && cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty() && !contains_return_break_continue_macro(or_arm.body) { - Some(or_arm) + Some((or_arm, unwrap_arm)) } else { None } } + +fn lint<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'tcx>, + scrutinee: &'tcx Expr<'_>, + ty_name: &str, + or_body_snippet: String, + indent: usize, +) { + let reindented_or_body = reindent_multiline(or_body_snippet.into(), true, Some(indent)); + + let mut app = Applicability::MachineApplicable; + let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par(); + span_lint_and_sugg( + cx, + MANUAL_UNWRAP_OR, + expr.span, + format!("this pattern reimplements `{ty_name}::unwrap_or`"), + "replace with", + format!("{suggestion}.unwrap_or({reindented_or_body})",), + app, + ); +} diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 691ecd57535ac..bf7156cc53ec3 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1069,7 +1069,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { redundant_guards::check(cx, arms, &self.msrv); if !in_constant(cx, expr.hir_id) { - manual_unwrap_or::check(cx, expr, ex, arms); + manual_unwrap_or::check_match(cx, expr, ex, arms); manual_map::check_match(cx, expr, ex, arms); manual_filter::check_match(cx, ex, arms, expr); } @@ -1097,6 +1097,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches { ); } if !in_constant(cx, expr.hir_id) { + manual_unwrap_or::check_if_let( + cx, + expr, + if_let.let_pat, + if_let.let_expr, + if_let.if_then, + else_expr, + ); manual_map::check_if_let(cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr); manual_filter::check_if_let( cx, diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 69791414f72c6..99fdbcff890b4 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{expr_block, get_source_text, snippet}; +use clippy_utils::source::{expr_block, snippet, SpanRangeExt}; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item, peel_mid_ty_refs}; use clippy_utils::{is_lint_allowed, is_unit_expr, is_wild, peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs}; use core::cmp::max; @@ -17,7 +17,7 @@ use super::{MATCH_BOOL, SINGLE_MATCH, SINGLE_MATCH_ELSE}; /// span, e.g. a string literal `"//"`, but we know that this isn't the case for empty /// match arms. fn empty_arm_has_comment(cx: &LateContext<'_>, span: Span) -> bool { - if let Some(ff) = get_source_text(cx, span) + if let Some(ff) = span.get_source_text(cx) && let Some(text) = ff.as_str() { text.as_bytes().windows(2).any(|w| w == b"//" || w == b"/*") diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs new file mode 100644 index 0000000000000..e3ce64c246a54 --- /dev/null +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -0,0 +1,238 @@ +use clippy_config::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::{IntoSpan, SpanRangeExt}; +use clippy_utils::ty::get_field_by_name; +use clippy_utils::visitors::{for_each_expr, for_each_expr_without_closures}; +use clippy_utils::{expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, ExprUseNode}; +use core::ops::ControlFlow; +use rustc_errors::Applicability; +use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; + +use super::MANUAL_INSPECT; + +#[expect(clippy::too_many_lines)] +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: &Msrv) { + if let ExprKind::Closure(c) = arg.kind + && matches!(c.kind, ClosureKind::Closure) + && let typeck = cx.typeck_results() + && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id) + && (is_diag_trait_item(cx, fn_id, sym::Iterator) + || (msrv.meets(msrvs::OPTION_RESULT_INSPECT) + && (is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result)))) + && let body = cx.tcx.hir().body(c.body) + && let [param] = body.params + && let PatKind::Binding(BindingMode(ByRef::No, Mutability::Not), arg_id, _, None) = param.pat.kind + && let arg_ty = typeck.node_type(arg_id) + && let ExprKind::Block(block, _) = body.value.kind + && let Some(final_expr) = block.expr + && !block.stmts.is_empty() + && path_to_local_id(final_expr, arg_id) + && typeck.expr_adjustments(final_expr).is_empty() + { + let mut requires_copy = false; + let mut requires_deref = false; + + // The number of unprocessed return expressions. + let mut ret_count = 0u32; + + // The uses for which processing is delayed until after the visitor. + let mut delayed = vec![]; + + let ctxt = arg.span.ctxt(); + let can_lint = for_each_expr_without_closures(block.stmts, |e| { + if let ExprKind::Closure(c) = e.kind { + // Nested closures don't need to treat returns specially. + let _: Option = for_each_expr(cx, cx.tcx.hir().body(c.body).value, |e| { + if path_to_local_id(e, arg_id) { + let (kind, same_ctxt) = check_use(cx, e); + match (kind, same_ctxt && e.span.ctxt() == ctxt) { + (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { + requires_copy = true; + requires_deref = true; + }, + (UseKind::AutoBorrowed, true) => {}, + (UseKind::WillAutoDeref, true) => { + requires_copy = true; + }, + (kind, true) => delayed.push(kind), + } + } + ControlFlow::Continue(()) + }); + } else if matches!(e.kind, ExprKind::Ret(_)) { + ret_count += 1; + } else if path_to_local_id(e, arg_id) { + let (kind, same_ctxt) = check_use(cx, e); + match (kind, same_ctxt && e.span.ctxt() == ctxt) { + (UseKind::Return(..), false) => { + return ControlFlow::Break(()); + }, + (_, false) | (UseKind::Deref, true) => { + requires_copy = true; + requires_deref = true; + }, + (UseKind::AutoBorrowed, true) => {}, + (UseKind::WillAutoDeref, true) => { + requires_copy = true; + }, + (kind @ UseKind::Return(_), true) => { + ret_count -= 1; + delayed.push(kind); + }, + (kind, true) => delayed.push(kind), + } + } + ControlFlow::Continue(()) + }) + .is_none(); + + if ret_count != 0 { + // A return expression that didn't return the original value was found. + return; + } + + let mut edits = Vec::with_capacity(delayed.len() + 3); + let mut addr_of_edits = Vec::with_capacity(delayed.len()); + for x in delayed { + match x { + UseKind::Return(s) => edits.push((s.with_leading_whitespace(cx).with_ctxt(s.ctxt()), String::new())), + UseKind::Borrowed(s) => { + #[expect(clippy::range_plus_one)] + let range = s.map_range(cx, |src, range| { + let src = src.get(range.clone())?; + let trimmed = src.trim_start_matches([' ', '\t', '\n', '\r', '(']); + trimmed.starts_with('&').then(|| { + let pos = range.start + src.len() - trimmed.len(); + pos..pos + 1 + }) + }); + if let Some(range) = range { + addr_of_edits.push((range.with_ctxt(s.ctxt()), String::new())); + } else { + requires_copy = true; + requires_deref = true; + } + }, + UseKind::FieldAccess(name, e) => { + let Some(mut ty) = get_field_by_name(cx.tcx, arg_ty.peel_refs(), name) else { + requires_copy = true; + continue; + }; + let mut prev_expr = e; + + for (_, parent) in cx.tcx.hir().parent_iter(e.hir_id) { + if let Node::Expr(e) = parent { + match e.kind { + ExprKind::Field(_, name) + if let Some(fty) = get_field_by_name(cx.tcx, ty.peel_refs(), name.name) => + { + ty = fty; + prev_expr = e; + continue; + }, + ExprKind::AddrOf(BorrowKind::Ref, ..) => break, + _ if matches!( + typeck.expr_adjustments(prev_expr).first(), + Some(Adjustment { + kind: Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)) + | Adjust::Deref(_), + .. + }) + ) => + { + break; + }, + _ => {}, + } + } + requires_copy |= !ty.is_copy_modulo_regions(cx.tcx, cx.param_env); + break; + } + }, + // Already processed uses. + UseKind::AutoBorrowed | UseKind::WillAutoDeref | UseKind::Deref => {}, + } + } + + if can_lint + && (!requires_copy || arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env)) + // This case could be handled, but a fair bit of care would need to be taken. + && (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.param_env)) + { + if requires_deref { + edits.push((param.span.shrink_to_lo(), "&".into())); + } else { + edits.extend(addr_of_edits); + } + edits.push(( + name_span, + String::from(match name { + "map" => "inspect", + "map_err" => "inspect_err", + _ => return, + }), + )); + edits.push(( + final_expr + .span + .with_leading_whitespace(cx) + .with_ctxt(final_expr.span.ctxt()), + String::new(), + )); + let app = if edits.iter().any(|(s, _)| s.from_expansion()) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| { + diag.multipart_suggestion("try", edits, app); + }); + } + } +} + +enum UseKind<'tcx> { + AutoBorrowed, + WillAutoDeref, + Deref, + Return(Span), + Borrowed(Span), + FieldAccess(Symbol, &'tcx Expr<'tcx>), +} + +/// Checks how the value is used, and whether it was used in the same `SyntaxContext`. +fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { + let use_cx = expr_use_ctxt(cx, e); + if use_cx + .adjustments + .first() + .is_some_and(|a| matches!(a.kind, Adjust::Deref(_))) + { + return (UseKind::AutoBorrowed, use_cx.same_ctxt); + } + let res = match use_cx.use_node(cx) { + ExprUseNode::Return(_) => { + if let ExprKind::Ret(Some(e)) = use_cx.node.expect_expr().kind { + UseKind::Return(e.span) + } else { + return (UseKind::Return(DUMMY_SP), false); + } + }, + ExprUseNode::FieldAccess(name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()), + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) + if use_cx + .adjustments + .first() + .is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)))) => + { + UseKind::AutoBorrowed + }, + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) => UseKind::WillAutoDeref, + ExprUseNode::AddrOf(BorrowKind::Ref, _) => UseKind::Borrowed(use_cx.node.expect_expr().span), + _ => UseKind::Deref, + }; + (res, use_cx.same_ctxt) +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 6200716afbe99..1408f45482007 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -53,6 +53,7 @@ mod iter_with_drain; mod iterator_step_by_zero; mod join_absolute_paths; mod manual_c_str_literals; +mod manual_inspect; mod manual_is_variant_and; mod manual_next_back; mod manual_ok_or; @@ -116,6 +117,7 @@ mod unnecessary_iter_cloned; mod unnecessary_join; mod unnecessary_lazy_eval; mod unnecessary_literal_unwrap; +mod unnecessary_min_or_max; mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; @@ -3944,6 +3946,31 @@ declare_clippy_lint! { "cloning an `Option` via `as_ref().cloned()`" } +declare_clippy_lint! { + /// ### What it does + /// Checks for unnecessary calls to `min()` or `max()` in the following cases + /// - Either both side is constant + /// - One side is clearly larger than the other, like i32::MIN and an i32 variable + /// + /// ### Why is this bad? + /// + /// In the aformentioned cases it is not necessary to call `min()` or `max()` + /// to compare values, it may even cause confusion. + /// + /// ### Example + /// ```no_run + /// let _ = 0.min(7_u32); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = 0; + /// ``` + #[clippy::version = "1.78.0"] + pub UNNECESSARY_MIN_OR_MAX, + complexity, + "using 'min()/max()' when there is no need for it" +} + declare_clippy_lint! { /// ### What it does /// Checks for usage of `.map_or_else()` "map closure" for `Result` type. @@ -4079,6 +4106,27 @@ declare_clippy_lint! { "is_ascii() called on a char iterator" } +declare_clippy_lint! { + /// ### What it does + /// Checks for uses of `map` which return the original item. + /// + /// ### Why is this bad? + /// `inspect` is both clearer in intent and shorter. + /// + /// ### Example + /// ```no_run + /// let x = Some(0).map(|x| { println!("{x}"); x }); + /// ``` + /// Use instead: + /// ```no_run + /// let x = Some(0).inspect(|x| println!("{x}")); + /// ``` + #[clippy::version = "1.78.0"] + pub MANUAL_INSPECT, + complexity, + "use of `map` returning the original item" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4244,6 +4292,8 @@ impl_lint_pass!(Methods => [ MANUAL_C_STR_LITERALS, UNNECESSARY_GET_THEN_CHECK, NEEDLESS_CHARACTER_ITERATION, + MANUAL_INSPECT, + UNNECESSARY_MIN_OR_MAX, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4543,6 +4593,9 @@ impl Methods { Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2), _ => {}, }, + ("min" | "max", [arg]) => { + unnecessary_min_or_max::check(cx, expr, name, recv, arg); + }, ("drain", ..) => { if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.parent_hir_node(expr.hir_id) && matches!(kind, StmtKind::Semi(_)) @@ -4747,6 +4800,7 @@ impl Methods { } } map_identity::check(cx, expr, recv, m_arg, name, span); + manual_inspect::check(cx, expr, m_arg, name, span, &self.msrv); }, ("map_or", [def, map]) => { option_map_or_none::check(cx, expr, recv, def, map); diff --git a/clippy_lints/src/methods/unnecessary_min_or_max.rs b/clippy_lints/src/methods/unnecessary_min_or_max.rs new file mode 100644 index 0000000000000..78851d4122f12 --- /dev/null +++ b/clippy_lints/src/methods/unnecessary_min_or_max.rs @@ -0,0 +1,90 @@ +use std::cmp::Ordering; + +use super::UNNECESSARY_MIN_OR_MAX; +use clippy_utils::diagnostics::span_lint_and_sugg; + +use clippy_utils::consts::{constant, constant_with_source, Constant, ConstantSource, FullInt}; +use clippy_utils::source::snippet; + +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_middle::ty; +use rustc_span::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + name: &str, + recv: &'tcx Expr<'_>, + arg: &'tcx Expr<'_>, +) { + let typeck_results = cx.typeck_results(); + if let Some((left, ConstantSource::Local | ConstantSource::CoreConstant)) = + constant_with_source(cx, typeck_results, recv) + && let Some((right, ConstantSource::Local | ConstantSource::CoreConstant)) = + constant_with_source(cx, typeck_results, arg) + { + let Some(ord) = Constant::partial_cmp(cx.tcx, typeck_results.expr_ty(recv), &left, &right) else { + return; + }; + + lint(cx, expr, name, recv.span, arg.span, ord); + } else if let Some(extrema) = detect_extrema(cx, recv) { + let ord = match extrema { + Extrema::Minimum => Ordering::Less, + Extrema::Maximum => Ordering::Greater, + }; + lint(cx, expr, name, recv.span, arg.span, ord); + } else if let Some(extrema) = detect_extrema(cx, arg) { + let ord = match extrema { + Extrema::Minimum => Ordering::Greater, + Extrema::Maximum => Ordering::Less, + }; + lint(cx, expr, name, recv.span, arg.span, ord); + } +} + +fn lint(cx: &LateContext<'_>, expr: &Expr<'_>, name: &str, lhs: Span, rhs: Span, order: Ordering) { + let cmp_str = if order.is_ge() { "smaller" } else { "greater" }; + + let suggested_value = if (name == "min" && order.is_ge()) || (name == "max" && order.is_le()) { + snippet(cx, rhs, "..") + } else { + snippet(cx, lhs, "..") + }; + + span_lint_and_sugg( + cx, + UNNECESSARY_MIN_OR_MAX, + expr.span, + format!( + "`{}` is never {} than `{}` and has therefore no effect", + snippet(cx, lhs, ".."), + cmp_str, + snippet(cx, rhs, "..") + ), + "try", + suggested_value.to_string(), + Applicability::MachineApplicable, + ); +} + +#[derive(Debug)] +enum Extrema { + Minimum, + Maximum, +} +fn detect_extrema<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option { + let ty = cx.typeck_results().expr_ty(expr); + + let cv = constant(cx, cx.typeck_results(), expr)?; + + match (cv.int_value(cx, ty)?, ty.kind()) { + (FullInt::S(i), &ty::Int(ity)) if i == i128::MIN >> (128 - ity.bit_width()?) => Some(Extrema::Minimum), + (FullInt::S(i), &ty::Int(ity)) if i == i128::MAX >> (128 - ity.bit_width()?) => Some(Extrema::Maximum), + (FullInt::U(i), &ty::Uint(uty)) if i == u128::MAX >> (128 - uty.bit_width()?) => Some(Extrema::Maximum), + (FullInt::U(0), &ty::Uint(_)) => Some(Extrema::Minimum), + _ => None, + } +} diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 4592324809fa2..bb0d714a31fd4 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -1,11 +1,11 @@ use clippy_config::msrvs::{self, Msrv}; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::qualify_min_const_fn::is_min_const_fn; use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_from_proc_macro, trait_ref_of_method}; -use rustc_hir as hir; +use rustc_errors::Applicability; use rustc_hir::def_id::CRATE_DEF_ID; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, Constness, FnDecl, GenericParamKind}; +use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; @@ -120,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { } }, FnKind::Method(_, sig, ..) => { - if trait_ref_of_method(cx, def_id).is_some() || already_const(sig.header) { + if already_const(sig.header) || trait_ref_of_method(cx, def_id).is_some() { return; } }, @@ -147,10 +147,22 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { let mir = cx.tcx.optimized_mir(def_id); - if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) { - span_lint(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`"); + if let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv) + && let hir::Node::Item(hir::Item { vis_span, .. }) | hir::Node::ImplItem(hir::ImplItem { vis_span, .. }) = + cx.tcx.hir_node_by_def_id(def_id) + { + let suggestion = if vis_span.is_empty() { "const " } else { " const" }; + span_lint_and_then(cx, MISSING_CONST_FOR_FN, span, "this could be a `const fn`", |diag| { + diag.span_suggestion_verbose( + vis_span.shrink_to_hi(), + "make the function `const`", + suggestion, + Applicability::MachineApplicable, + ); + }); } } + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index ca344dc5c8109..250fd5cbd483b 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -8,7 +8,7 @@ use clippy_utils::attrs::is_doc_hidden; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_from_proc_macro; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::SpanRangeExt; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; @@ -266,8 +266,5 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { } fn span_to_snippet_contains_docs(cx: &LateContext<'_>, search_span: Span) -> bool { - let Some(snippet) = snippet_opt(cx, search_span) else { - return false; - }; - snippet.lines().rev().any(|line| line.trim().starts_with("///")) + search_span.check_source_text(cx, |src| src.lines().rev().any(|line| line.trim().starts_with("///"))) } diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs index d608f3bf7b4d5..d276e29bacecf 100644 --- a/clippy_lints/src/multiple_bound_locations.rs +++ b/clippy_lints/src/multiple_bound_locations.rs @@ -6,7 +6,7 @@ use rustc_session::declare_lint_pass; use rustc_span::Span; use clippy_utils::diagnostics::span_lint; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::SpanRangeExt; declare_clippy_lint! { /// ### What it does @@ -54,8 +54,10 @@ impl EarlyLintPass for MultipleBoundLocations { match clause { WherePredicate::BoundPredicate(pred) => { if (!pred.bound_generic_params.is_empty() || !pred.bounds.is_empty()) - && let Some(name) = snippet_opt(cx, pred.bounded_ty.span) - && let Some(bound_span) = generic_params_with_bounds.get(name.as_str()) + && let Some(Some(bound_span)) = pred + .bounded_ty + .span + .with_source_text(cx, |src| generic_params_with_bounds.get(src)) { emit_lint(cx, *bound_span, pred.bounded_ty.span); } diff --git a/clippy_lints/src/needless_borrows_for_generic_args.rs b/clippy_lints/src/needless_borrows_for_generic_args.rs index f4846a1753f7c..064ce59c234e4 100644 --- a/clippy_lints/src/needless_borrows_for_generic_args.rs +++ b/clippy_lints/src/needless_borrows_for_generic_args.rs @@ -80,11 +80,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if matches!(expr.kind, ExprKind::AddrOf(..)) && !expr.span.from_expansion() - && let Some(use_cx) = expr_use_ctxt(cx, expr) + && let use_cx = expr_use_ctxt(cx, expr) + && use_cx.same_ctxt && !use_cx.is_ty_unified - && let Some(DefinedTy::Mir(ty)) = use_cx.node.defined_ty(cx) + && let use_node = use_cx.use_node(cx) + && let Some(DefinedTy::Mir(ty)) = use_node.defined_ty(cx) && let ty::Param(ty) = *ty.value.skip_binder().kind() - && let Some((hir_id, fn_id, i)) = match use_cx.node { + && let Some((hir_id, fn_id, i)) = match use_node { ExprUseNode::MethodArg(_, _, 0) => None, ExprUseNode::MethodArg(hir_id, None, i) => cx .typeck_results() diff --git a/clippy_lints/src/needless_else.rs b/clippy_lints/src/needless_else.rs index b6aad69d1668c..f8bb72a16db2b 100644 --- a/clippy_lints/src/needless_else.rs +++ b/clippy_lints/src/needless_else.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_opt, trim_span}; +use clippy_utils::source::{IntoSpan, SpanRangeExt}; use rustc_ast::ast::{Expr, ExprKind}; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -41,16 +41,16 @@ impl EarlyLintPass for NeedlessElse { && !expr.span.from_expansion() && !else_clause.span.from_expansion() && block.stmts.is_empty() - && let Some(trimmed) = expr.span.trim_start(then_block.span) - && let span = trim_span(cx.sess().source_map(), trimmed) - && let Some(else_snippet) = snippet_opt(cx, span) - // Ignore else blocks that contain comments or #[cfg]s - && !else_snippet.contains(['/', '#']) + && let range = (then_block.span.hi()..expr.span.hi()).trim_start(cx) + && range.clone().check_source_text(cx, |src| { + // Ignore else blocks that contain comments or #[cfg]s + !src.contains(['/', '#']) + }) { span_lint_and_sugg( cx, NEEDLESS_ELSE, - span, + range.with_ctxt(expr.span.ctxt()), "this `else` branch is empty", "you can remove it", String::new(), diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index 630018238f4c4..143acc2b1cb76 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -25,14 +25,14 @@ declare_clippy_lint! { /// ```no_run /// let v = vec![0, 1, 2]; /// v.iter().for_each(|elem| { - /// println!("{}", elem); + /// println!("{elem}"); /// }) /// ``` /// Use instead: /// ```no_run /// let v = vec![0, 1, 2]; - /// for elem in v.iter() { - /// println!("{}", elem); + /// for elem in &v { + /// println!("{elem}"); /// } /// ``` /// diff --git a/clippy_lints/src/needless_if.rs b/clippy_lints/src/needless_if.rs index 51bee4b51f6f9..1d6233d432a75 100644 --- a/clippy_lints/src/needless_if.rs +++ b/clippy_lints/src/needless_if.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher::If; use clippy_utils::is_from_proc_macro; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::{snippet_opt, SpanRangeExt}; use rustc_errors::Applicability; use rustc_hir::{ExprKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -39,18 +39,24 @@ declare_lint_pass!(NeedlessIf => [NEEDLESS_IF]); impl LateLintPass<'_> for NeedlessIf { fn check_stmt<'tcx>(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { if let StmtKind::Expr(expr) = stmt.kind - && let Some(If {cond, then, r#else: None }) = If::hir(expr) + && let Some(If { + cond, + then, + r#else: None, + }) = If::hir(expr) && let ExprKind::Block(block, ..) = then.kind && block.stmts.is_empty() && block.expr.is_none() && !in_external_macro(cx.sess(), expr.span) - && let Some(then_snippet) = snippet_opt(cx, then.span) - // Ignore - // - empty macro expansions - // - empty reptitions in macro expansions - // - comments - // - #[cfg]'d out code - && then_snippet.chars().all(|ch| matches!(ch, '{' | '}') || ch.is_ascii_whitespace()) + && then.span.check_source_text(cx, |src| { + // Ignore + // - empty macro expansions + // - empty reptitions in macro expansions + // - comments + // - #[cfg]'d out code + src.bytes() + .all(|ch| matches!(ch, b'{' | b'}') || ch.is_ascii_whitespace()) + }) && let Some(cond_snippet) = snippet_opt(cx, cond.span) && !is_from_proc_macro(cx, expr) { diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 2701d6bdca391..b915df527629e 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::{snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{snippet_with_applicability, SpanRangeExt}; use clippy_utils::{match_def_path, paths}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; @@ -53,8 +53,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) - && let Some(snip) = snippet_opt(cx, param.span) - && !(snip.starts_with("0o") || snip.starts_with("0b")) + && param + .span + .check_source_text(cx, |src| !matches!(src.as_bytes(), [b'0', b'o' | b'b', ..])) { show_error(cx, param); } @@ -65,8 +66,9 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { && match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) - && let Some(snip) = snippet_opt(cx, param.span) - && !(snip.starts_with("0o") || snip.starts_with("0b")) + && param + .span + .check_source_text(cx, |src| !matches!(src.as_bytes(), [b'0', b'o' | b'b', ..])) { show_error(cx, param); } diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index 2fc039ae886e4..2eae9b23746df 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_then; -use rustc_ast::ast::{Expr, ExprKind}; -use rustc_ast::token::{Lit, LitKind}; +use clippy_utils::source::SpanRangeExt; +use rustc_ast::token::LitKind; +use rustc_ast::{Expr, ExprKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::declare_lint_pass; -use rustc_span::Span; -use std::fmt::Write; +use rustc_span::{BytePos, Pos, SpanData}; declare_clippy_lint! { /// ### What it does @@ -52,104 +52,66 @@ declare_lint_pass!(OctalEscapes => [OCTAL_ESCAPES]); impl EarlyLintPass for OctalEscapes { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if let ExprKind::Lit(token_lit) = &expr.kind { - if matches!(token_lit.kind, LitKind::Str) { - check_lit(cx, token_lit, expr.span, true); - } else if matches!(token_lit.kind, LitKind::ByteStr) { - check_lit(cx, token_lit, expr.span, false); - } - } - } -} - -fn check_lit(cx: &EarlyContext<'_>, lit: &Lit, span: Span, is_string: bool) { - let contents = lit.symbol.as_str(); - let mut iter = contents.char_indices().peekable(); - let mut found = vec![]; + if let ExprKind::Lit(lit) = &expr.kind + // The number of bytes from the start of the token to the start of literal's text. + && let start_offset = BytePos::from_u32(match lit.kind { + LitKind::Str => 1, + LitKind::ByteStr | LitKind::CStr => 2, + _ => return, + }) + && !in_external_macro(cx.sess(), expr.span) + { + let s = lit.symbol.as_str(); + let mut iter = s.as_bytes().iter(); + while let Some(&c) = iter.next() { + if c == b'\\' + // Always move the iterator to read the escape char. + && let Some(b'0') = iter.next() + { + // C-style octal escapes read from one to three characters. + // The first character (`0`) has already been read. + let (tail, len, c_hi, c_lo) = match *iter.as_slice() { + [c_hi @ b'0'..=b'7', c_lo @ b'0'..=b'7', ref tail @ ..] => (tail, 4, c_hi, c_lo), + [c_lo @ b'0'..=b'7', ref tail @ ..] => (tail, 3, b'0', c_lo), + _ => continue, + }; + iter = tail.iter(); + let offset = start_offset + BytePos::from_usize(s.len() - tail.len()); + let data = expr.span.data(); + let span = SpanData { + lo: data.lo + offset - BytePos::from_u32(len), + hi: data.lo + offset, + ..data + } + .span(); - // go through the string, looking for \0[0-7][0-7]? - while let Some((from, ch)) = iter.next() { - if ch == '\\' { - if let Some((_, '0')) = iter.next() { - // collect up to two further octal digits - if let Some((mut to, _)) = iter.next_if(|(_, ch)| matches!(ch, '0'..='7')) { - if iter.next_if(|(_, ch)| matches!(ch, '0'..='7')).is_some() { - to += 1; + // Last check to make sure the source text matches what we read from the string. + // Macros are involved somehow if this doesn't match. + if span.check_source_text(cx, |src| match *src.as_bytes() { + [b'\\', b'0', lo] => lo == c_lo, + [b'\\', b'0', hi, lo] => hi == c_hi && lo == c_lo, + _ => false, + }) { + span_lint_and_then(cx, OCTAL_ESCAPES, span, "octal-looking escape in a literal", |diag| { + diag.help_once("octal escapes are not supported, `\\0` is always null") + .span_suggestion( + span, + "if an octal escape is intended, use a hex escape instead", + format!("\\x{:02x}", (((c_hi - b'0') << 3) | (c_lo - b'0'))), + Applicability::MaybeIncorrect, + ) + .span_suggestion( + span, + "if a null escape is intended, disambiguate using", + format!("\\x00{}{}", c_hi as char, c_lo as char), + Applicability::MaybeIncorrect, + ); + }); + } else { + break; } - found.push((from, to + 1)); } } } } - - if found.is_empty() { - return; - } - - span_lint_and_then( - cx, - OCTAL_ESCAPES, - span, - format!( - "octal-looking escape in {} literal", - if is_string { "string" } else { "byte string" } - ), - |diag| { - diag.help(format!( - "octal escapes are not supported, `\\0` is always a null {}", - if is_string { "character" } else { "byte" } - )); - - // Generate suggestions if the string is not too long (~ 5 lines) - if contents.len() < 400 { - // construct two suggestion strings, one with \x escapes with octal meaning - // as in C, and one with \x00 for null bytes. - let mut suggest_1 = if is_string { "\"" } else { "b\"" }.to_string(); - let mut suggest_2 = suggest_1.clone(); - let mut index = 0; - for (from, to) in found { - suggest_1.push_str(&contents[index..from]); - suggest_2.push_str(&contents[index..from]); - - // construct a replacement escape - // the maximum value is \077, or \x3f, so u8 is sufficient here - if let Ok(n) = u8::from_str_radix(&contents[from + 1..to], 8) { - write!(suggest_1, "\\x{n:02x}").unwrap(); - } - - // append the null byte as \x00 and the following digits literally - suggest_2.push_str("\\x00"); - suggest_2.push_str(&contents[from + 2..to]); - - index = to; - } - suggest_1.push_str(&contents[index..]); - suggest_2.push_str(&contents[index..]); - - suggest_1.push('"'); - suggest_2.push('"'); - // suggestion 1: equivalent hex escape - diag.span_suggestion( - span, - "if an octal escape was intended, use the hexadecimal representation instead", - suggest_1, - Applicability::MaybeIncorrect, - ); - // suggestion 2: unambiguous null byte - diag.span_suggestion( - span, - format!( - "if the null {} is intended, disambiguate using", - if is_string { "character" } else { "byte" } - ), - suggest_2, - Applicability::MaybeIncorrect, - ); - } - }, - ); } diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index 186e548d37304..4fdaa9f00a19c 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -1,7 +1,7 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::consts::{constant, Constant}; use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability}; +use clippy_utils::source::{snippet, snippet_with_applicability, SpanRangeExt}; use clippy_utils::sugg::Sugg; use clippy_utils::{get_parent_expr, higher, in_constant, is_integer_const, path_to_local}; use rustc_ast::ast::RangeLimits; @@ -285,9 +285,10 @@ fn check_possible_range_contains( if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind && op == lhs_op.node && let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent()) - && let Some(snip) = &snippet_opt(cx, new_span) - // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong - && snip.matches('(').count() == snip.matches(')').count() + && new_span.check_source_text(cx, |src| { + // Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong + src.matches('(').count() == src.matches(')').count() + }) { check_possible_range_contains(cx, op, new_lhs, right, expr, new_span); } @@ -363,17 +364,19 @@ fn check_exclusive_range_plus_one(cx: &LateContext<'_>, expr: &Expr<'_>) { |diag| { let start = start.map_or(String::new(), |x| Sugg::hir(cx, x, "x").maybe_par().to_string()); let end = Sugg::hir(cx, y, "y").maybe_par(); - if let Some(is_wrapped) = &snippet_opt(cx, span) { - if is_wrapped.starts_with('(') && is_wrapped.ends_with(')') { + match span.with_source_text(cx, |src| src.starts_with('(') && src.ends_with(')')) { + Some(true) => { diag.span_suggestion(span, "use", format!("({start}..={end})"), Applicability::MaybeIncorrect); - } else { + }, + Some(false) => { diag.span_suggestion( span, "use", format!("{start}..={end}"), Applicability::MachineApplicable, // snippet ); - } + }, + None => {}, } }, ); diff --git a/clippy_lints/src/string_patterns.rs b/clippy_lints/src/string_patterns.rs index 64b5b8f9f27b4..7ba58942a175e 100644 --- a/clippy_lints/src/string_patterns.rs +++ b/clippy_lints/src/string_patterns.rs @@ -1,5 +1,6 @@ use std::ops::ControlFlow; +use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::macros::matching_root_macro_call; @@ -12,7 +13,7 @@ use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -69,7 +70,18 @@ declare_clippy_lint! { "using a single-character str where a char could be used, e.g., `_.split(\"x\")`" } -declare_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]); +pub struct StringPatterns { + msrv: Msrv, +} + +impl StringPatterns { + #[must_use] + pub fn new(msrv: Msrv) -> Self { + Self { msrv } + } +} + +impl_lint_pass!(StringPatterns => [MANUAL_PATTERN_CHAR_COMPARISON, SINGLE_CHAR_PATTERN]); const PATTERN_METHODS: [(&str, usize); 22] = [ ("contains", 0), @@ -122,7 +134,7 @@ fn get_char_span<'tcx>(cx: &'_ LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio } } -fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>) { +fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr<'_>, msrv: &Msrv) { if let ExprKind::Closure(closure) = method_arg.kind && let body = cx.tcx.hir().body(closure.body) && let Some(PatKind::Binding(_, binding, ..)) = body.params.first().map(|p| p.pat.kind) @@ -178,6 +190,9 @@ fn check_manual_pattern_char_comparison(cx: &LateContext<'_>, method_arg: &Expr< { return; } + if set_char_spans.len() > 1 && !msrv.meets(msrvs::PATTERN_TRAIT_CHAR_ARRAY) { + return; + } span_lint_and_then( cx, MANUAL_PATTERN_CHAR_COMPARISON, @@ -221,7 +236,9 @@ impl<'tcx> LateLintPass<'tcx> for StringPatterns { { check_single_char_pattern_lint(cx, arg); - check_manual_pattern_char_comparison(cx, arg); + check_manual_pattern_char_comparison(cx, arg, &self.msrv); } } + + extract_msrv_attr!(LateContext); } diff --git a/clippy_lints/src/upper_case_acronyms.rs b/clippy_lints/src/upper_case_acronyms.rs index f376d3496461f..72392f8e1f741 100644 --- a/clippy_lints/src/upper_case_acronyms.rs +++ b/clippy_lints/src/upper_case_acronyms.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; -use itertools::Itertools; +use core::mem::replace; use rustc_errors::Applicability; use rustc_hir::{HirId, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -56,55 +56,71 @@ impl UpperCaseAcronyms { impl_lint_pass!(UpperCaseAcronyms => [UPPER_CASE_ACRONYMS]); -fn correct_ident(ident: &str) -> String { - let ident = ident.chars().rev().collect::(); - let fragments = ident - .split_inclusive(|x: char| !x.is_ascii_lowercase()) - .rev() - .map(|x| x.chars().rev().collect::()); - - let mut ident = fragments.clone().next().unwrap(); - for (ref prev, ref curr) in fragments.tuple_windows() { - if <[&String; 2]>::from((prev, curr)) - .iter() - .all(|s| s.len() == 1 && s.chars().next().unwrap().is_ascii_uppercase()) - { - ident.push_str(&curr.to_ascii_lowercase()); +fn contains_acronym(s: &str) -> bool { + let mut count = 0; + for c in s.chars() { + if c.is_ascii_uppercase() { + count += 1; + if count == 3 { + return true; + } } else { - ident.push_str(curr); + count = 0; } } - ident + count == 2 } fn check_ident(cx: &LateContext<'_>, ident: &Ident, hir_id: HirId, be_aggressive: bool) { - let span = ident.span; - let ident = ident.as_str(); - let corrected = correct_ident(ident); - // warn if we have pure-uppercase idents - // assume that two-letter words are some kind of valid abbreviation like FP for false positive - // (and don't warn) - if (ident.chars().all(|c| c.is_ascii_uppercase()) && ident.len() > 2) - // otherwise, warn if we have SOmeTHING lIKE THIs but only warn with the aggressive - // upper-case-acronyms-aggressive config option enabled - || (be_aggressive && ident != corrected) + let s = ident.as_str(); + + // By default, only warn for upper case identifiers with at least 3 characters. + let replacement = if s.len() > 2 && s.bytes().all(|c| c.is_ascii_uppercase()) { + let mut r = String::with_capacity(s.len()); + let mut s = s.chars(); + r.push(s.next().unwrap()); + r.extend(s.map(|c| c.to_ascii_lowercase())); + r + } else if be_aggressive + // Only lint if the ident starts with an upper case character. + && let unprefixed = s.trim_start_matches('_') + && unprefixed.starts_with(|c: char| c.is_ascii_uppercase()) + && contains_acronym(unprefixed) { - span_lint_hir_and_then( - cx, - UPPER_CASE_ACRONYMS, - hir_id, - span, - format!("name `{ident}` contains a capitalized acronym"), - |diag| { - diag.span_suggestion( - span, - "consider making the acronym lowercase, except the initial letter", - corrected, - Applicability::MaybeIncorrect, - ); - }, - ); - } + let mut r = String::with_capacity(s.len()); + let mut s = s.chars(); + let mut prev_upper = false; + while let Some(c) = s.next() { + r.push( + if replace(&mut prev_upper, c.is_ascii_uppercase()) + && s.clone().next().map_or(true, |c| c.is_ascii_uppercase()) + { + c.to_ascii_lowercase() + } else { + c + }, + ); + } + r + } else { + return; + }; + + span_lint_hir_and_then( + cx, + UPPER_CASE_ACRONYMS, + hir_id, + ident.span, + format!("name `{ident}` contains a capitalized acronym"), + |diag| { + diag.span_suggestion( + ident.span, + "consider making the acronym lowercase, except the initial letter", + replacement, + Applicability::MaybeIncorrect, + ); + }, + ); } impl LateLintPass<'_> for UpperCaseAcronyms { diff --git a/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/clippy_lints/src/utils/internal_lints/invalid_paths.rs index c62ae8d718dba..0beb0bb8ed4c1 100644 --- a/clippy_lints/src/utils/internal_lints/invalid_paths.rs +++ b/clippy_lints/src/utils/internal_lints/invalid_paths.rs @@ -69,6 +69,7 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { SimplifiedType::Float(FloatTy::F64), SimplifiedType::Slice, SimplifiedType::Str, + SimplifiedType::Bool, ] .iter() .flat_map(|&ty| cx.tcx.incoherent_impls(ty).into_iter()) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 785d5ed5dbeba..13256c5557da1 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -724,9 +724,7 @@ pub fn eq_ty(l: &Ty, r: &Ty) -> bool { (Tup(l), Tup(r)) => over(l, r, |l, r| eq_ty(l, r)), (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), (TraitObject(lg, ls), TraitObject(rg, rs)) => ls == rs && over(lg, rg, eq_generic_bound), - (ImplTrait(_, lg), ImplTrait(_, rg)) => { - over(lg, rg, eq_generic_bound) - }, + (ImplTrait(_, lg), ImplTrait(_, rg)) => over(lg, rg, eq_generic_bound), (Typeof(l), Typeof(r)) => eq_expr(&l.value, &r.value), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), _ => false, diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 553e899997504..7f2234b310b42 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -18,8 +18,8 @@ use rustc_ast::AttrStyle; use rustc_hir::intravisit::FnKind; use rustc_hir::{ Block, BlockCheckMode, Body, Closure, Destination, Expr, ExprKind, FieldDef, FnHeader, FnRetTy, HirId, Impl, - ImplItem, ImplItemKind, IsAuto, Item, ItemKind, LoopSource, MatchSource, MutTy, Node, QPath, Safety, TraitItem, - TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, + ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, + TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, }; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::TyCtxt; @@ -121,6 +121,26 @@ fn qpath_search_pat(path: &QPath<'_>) -> (Pat, Pat) { } } +fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) { + let (head, tail) = match path.segments { + [head, .., tail] => (head, tail), + [p] => (p, p), + [] => return (Pat::Str(""), Pat::Str("")), + }; + ( + if head.ident.name == kw::PathRoot { + Pat::Str("::") + } else { + Pat::Sym(head.ident.name) + }, + if tail.args.is_some() { + Pat::Str(">") + } else { + Pat::Sym(tail.ident.name) + }, + ) +} + /// Get the search patterns to use for the given expression fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) { match e.kind { @@ -355,19 +375,21 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { } } +fn ident_search_pat(ident: Ident) -> (Pat, Pat) { + (Pat::Sym(ident.name), Pat::Sym(ident.name)) +} + pub trait WithSearchPat<'cx> { type Context: LintContext; fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat); fn span(&self) -> Span; } macro_rules! impl_with_search_pat { - ($cx:ident: $ty:ident with $fn:ident $(($tcx:ident))?) => { - impl<'cx> WithSearchPat<'cx> for $ty<'cx> { - type Context = $cx<'cx>; - #[allow(unused_variables)] - fn search_pat(&self, cx: &Self::Context) -> (Pat, Pat) { - $(let $tcx = cx.tcx;)? - $fn($($tcx,)? self) + (($cx_ident:ident: $cx_ty:ident<$cx_lt:lifetime>, $self:tt: $ty:ty) => $fn:ident($($args:tt)*)) => { + impl<$cx_lt> WithSearchPat<$cx_lt> for $ty { + type Context = $cx_ty<$cx_lt>; + fn search_pat(&$self, $cx_ident: &Self::Context) -> (Pat, Pat) { + $fn($($args)*) } fn span(&self) -> Span { self.span @@ -375,13 +397,17 @@ macro_rules! impl_with_search_pat { } }; } -impl_with_search_pat!(LateContext: Expr with expr_search_pat(tcx)); -impl_with_search_pat!(LateContext: Item with item_search_pat); -impl_with_search_pat!(LateContext: TraitItem with trait_item_search_pat); -impl_with_search_pat!(LateContext: ImplItem with impl_item_search_pat); -impl_with_search_pat!(LateContext: FieldDef with field_def_search_pat); -impl_with_search_pat!(LateContext: Variant with variant_search_pat); -impl_with_search_pat!(LateContext: Ty with ty_search_pat); +impl_with_search_pat!((cx: LateContext<'tcx>, self: Expr<'tcx>) => expr_search_pat(cx.tcx, self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Item<'_>) => item_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: TraitItem<'_>) => trait_item_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Attribute) => attr_search_pat(self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node)); +impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self)); impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { type Context = LateContext<'cx>; @@ -395,32 +421,6 @@ impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) { } } -// `Attribute` does not have the `hir` associated lifetime, so we cannot use the macro -impl<'cx> WithSearchPat<'cx> for &'cx Attribute { - type Context = LateContext<'cx>; - - fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) { - attr_search_pat(self) - } - - fn span(&self) -> Span { - self.span - } -} - -// `Ident` does not have the `hir` associated lifetime, so we cannot use the macro -impl<'cx> WithSearchPat<'cx> for Ident { - type Context = LateContext<'cx>; - - fn search_pat(&self, _cx: &Self::Context) -> (Pat, Pat) { - (Pat::Sym(self.name), Pat::Sym(self.name)) - } - - fn span(&self) -> Span { - self.span - } -} - /// Checks if the item likely came from a proc-macro. /// /// This should be called after `in_external_macro` and the initial pattern matching of the ast as diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 681c86f76d07c..de6ccfe476fb0 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1,6 +1,7 @@ #![allow(clippy::float_cmp)] -use crate::source::{get_source_text, walk_span_to_context}; +use crate::macros::HirNode; +use crate::source::{walk_span_to_context, SpanRangeExt}; use crate::{clip, is_direct_expn_of, sext, unsext}; use rustc_apfloat::ieee::{Half, Quad}; @@ -17,7 +18,7 @@ use rustc_middle::ty::{self, EarlyBinder, FloatTy, GenericArgsRef, IntTy, List, use rustc_middle::{bug, mir, span_bug}; use rustc_span::def_id::DefId; use rustc_span::symbol::{Ident, Symbol}; -use rustc_span::SyntaxContext; +use rustc_span::{sym, SyntaxContext}; use rustc_target::abi::Size; use std::cmp::Ordering; use std::hash::{Hash, Hasher}; @@ -326,6 +327,8 @@ pub enum ConstantSource { Local, /// The value is dependent on a defined constant. Constant, + /// The value is dependent on a constant defined in `core` crate. + CoreConstant, } impl ConstantSource { pub fn is_local(&self) -> bool { @@ -439,9 +442,19 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { ExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.lcx.tcx.hir().body(body).value), ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => { + let is_core_crate = if let Some(def_id) = self.lcx.qpath_res(qpath, e.hir_id()).opt_def_id() { + self.lcx.tcx.crate_name(def_id.krate) == sym::core + } else { + false + }; self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| { let result = mir_to_const(this.lcx, result)?; - this.source = ConstantSource::Constant; + // If source is already Constant we wouldn't want to override it with CoreConstant + this.source = if is_core_crate && !matches!(this.source, ConstantSource::Constant) { + ConstantSource::CoreConstant + } else { + ConstantSource::Constant + }; Some(result) }) }, @@ -683,7 +696,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { if let Some(expr_span) = walk_span_to_context(expr.span, span.ctxt) && let expr_lo = expr_span.lo() && expr_lo >= span.lo - && let Some(src) = get_source_text(self.lcx, span.lo..expr_lo) + && let Some(src) = (span.lo..expr_lo).get_source_text(self.lcx) && let Some(src) = src.as_str() { use rustc_lexer::TokenKind::{BlockComment, LineComment, OpenBrace, Semi, Whitespace}; diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 801a98521590d..277ba8427e054 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -218,51 +218,56 @@ pub struct Range<'a> { impl<'a> Range<'a> { /// Higher a `hir` range to something similar to `ast::ExprKind::Range`. + #[allow(clippy::similar_names)] pub fn hir(expr: &'a Expr<'_>) -> Option> { - /// Finds the field named `name` in the field. Always return `Some` for - /// convenience. - fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c Expr<'c>> { - let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr; - Some(expr) - } - match expr.kind { - ExprKind::Call(path, args) + ExprKind::Call(path, [arg1, arg2]) if matches!( path.kind, ExprKind::Path(QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..)) ) => { Some(Range { - start: Some(&args[0]), - end: Some(&args[1]), + start: Some(arg1), + end: Some(arg2), limits: ast::RangeLimits::Closed, }) }, - ExprKind::Struct(path, fields, None) => match &path { - QPath::LangItem(hir::LangItem::RangeFull, ..) => Some(Range { + ExprKind::Struct(path, fields, None) => match (path, fields) { + (QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range { start: None, end: None, limits: ast::RangeLimits::HalfOpen, }), - QPath::LangItem(hir::LangItem::RangeFrom, ..) => Some(Range { - start: Some(get_field("start", fields)?), - end: None, - limits: ast::RangeLimits::HalfOpen, - }), - QPath::LangItem(hir::LangItem::Range, ..) => Some(Range { - start: Some(get_field("start", fields)?), - end: Some(get_field("end", fields)?), - limits: ast::RangeLimits::HalfOpen, - }), - QPath::LangItem(hir::LangItem::RangeToInclusive, ..) => Some(Range { - start: None, - end: Some(get_field("end", fields)?), - limits: ast::RangeLimits::Closed, - }), - QPath::LangItem(hir::LangItem::RangeTo, ..) => Some(Range { + (QPath::LangItem(hir::LangItem::RangeFrom, ..), [field]) if field.ident.name == sym::start => { + Some(Range { + start: Some(field.expr), + end: None, + limits: ast::RangeLimits::HalfOpen, + }) + }, + (QPath::LangItem(hir::LangItem::Range, ..), [field1, field2]) => { + let (start, end) = match (field1.ident.name, field2.ident.name) { + (sym::start, sym::end) => (field1.expr, field2.expr), + (sym::end, sym::start) => (field2.expr, field1.expr), + _ => return None, + }; + Some(Range { + start: Some(start), + end: Some(end), + limits: ast::RangeLimits::HalfOpen, + }) + }, + (QPath::LangItem(hir::LangItem::RangeToInclusive, ..), [field]) if field.ident.name == sym::end => { + Some(Range { + start: None, + end: Some(field.expr), + limits: ast::RangeLimits::Closed, + }) + }, + (QPath::LangItem(hir::LangItem::RangeTo, ..), [field]) if field.ident.name == sym::end => Some(Range { start: None, - end: Some(get_field("end", fields)?), + end: Some(field.expr), limits: ast::RangeLimits::HalfOpen, }), _ => None, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 50dd8430ac069..8706cec5d388a 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1,6 +1,6 @@ use crate::consts::constant_simple; use crate::macros::macro_backtrace; -use crate::source::{get_source_text, snippet_opt, walk_span_to_context, SpanRange}; +use crate::source::{snippet_opt, walk_span_to_context, SpanRange, SpanRangeExt}; use crate::tokenize_with_text; use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::fx::FxHasher; @@ -1173,9 +1173,9 @@ fn eq_span_tokens( pred: impl Fn(TokenKind) -> bool, ) -> bool { fn f(cx: &LateContext<'_>, left: Range, right: Range, pred: impl Fn(TokenKind) -> bool) -> bool { - if let Some(lsrc) = get_source_text(cx, left) + if let Some(lsrc) = left.get_source_text(cx) && let Some(lsrc) = lsrc.as_str() - && let Some(rsrc) = get_source_text(cx, right) + && let Some(rsrc) = right.get_source_text(cx) && let Some(rsrc) = rsrc.as_str() { let pred = |t: &(_, _)| pred(t.0); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index ee43d95272a00..935a25d79319d 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2666,13 +2666,80 @@ pub enum DefinedTy<'tcx> { /// The context an expressions value is used in. pub struct ExprUseCtxt<'tcx> { /// The parent node which consumes the value. - pub node: ExprUseNode<'tcx>, + pub node: Node<'tcx>, + /// The child id of the node the value came from. + pub child_id: HirId, /// Any adjustments applied to the type. pub adjustments: &'tcx [Adjustment<'tcx>], - /// Whether or not the type must unify with another code path. + /// Whether the type must unify with another code path. pub is_ty_unified: bool, - /// Whether or not the value will be moved before it's used. + /// Whether the value will be moved before it's used. pub moved_before_use: bool, + /// Whether the use site has the same `SyntaxContext` as the value. + pub same_ctxt: bool, +} +impl<'tcx> ExprUseCtxt<'tcx> { + pub fn use_node(&self, cx: &LateContext<'tcx>) -> ExprUseNode<'tcx> { + match self.node { + Node::LetStmt(l) => ExprUseNode::LetStmt(l), + Node::ExprField(field) => ExprUseNode::Field(field), + + Node::Item(&Item { + kind: ItemKind::Static(..) | ItemKind::Const(..), + owner_id, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Const(..), + owner_id, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Const(..), + owner_id, + .. + }) => ExprUseNode::ConstStatic(owner_id), + + Node::Item(&Item { + kind: ItemKind::Fn(..), + owner_id, + .. + }) + | Node::TraitItem(&TraitItem { + kind: TraitItemKind::Fn(..), + owner_id, + .. + }) + | Node::ImplItem(&ImplItem { + kind: ImplItemKind::Fn(..), + owner_id, + .. + }) => ExprUseNode::Return(owner_id), + + Node::Expr(use_expr) => match use_expr.kind { + ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { + def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + }), + + ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), + ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == self.child_id) { + Some(i) => ExprUseNode::FnArg(func, i), + None => ExprUseNode::Callee, + }, + ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( + use_expr.hir_id, + name.args, + args.iter() + .position(|arg| arg.hir_id == self.child_id) + .map_or(0, |i| i + 1), + ), + ExprKind::Field(_, name) => ExprUseNode::FieldAccess(name), + ExprKind::AddrOf(kind, mutbl, _) => ExprUseNode::AddrOf(kind, mutbl), + _ => ExprUseNode::Other, + }, + _ => ExprUseNode::Other, + } + } } /// The node which consumes a value. @@ -2693,7 +2760,8 @@ pub enum ExprUseNode<'tcx> { Callee, /// Access of a field. FieldAccess(Ident), - Expr, + /// Borrow expression. + AddrOf(ast::BorrowKind, Mutability), Other, } impl<'tcx> ExprUseNode<'tcx> { @@ -2770,26 +2838,25 @@ impl<'tcx> ExprUseNode<'tcx> { let sig = cx.tcx.fn_sig(id).skip_binder(); Some(DefinedTy::Mir(cx.tcx.param_env(id).and(sig.input(i)))) }, - Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Expr | Self::Other => None, + Self::LetStmt(_) | Self::FieldAccess(..) | Self::Callee | Self::Other | Self::AddrOf(..) => None, } } } /// Gets the context an expression's value is used in. -pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Option> { +pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> ExprUseCtxt<'tcx> { let mut adjustments = [].as_slice(); let mut is_ty_unified = false; let mut moved_before_use = false; + let mut same_ctxt = true; let ctxt = e.span.ctxt(); - walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| { + let node = walk_to_expr_usage(cx, e, &mut |parent_id, parent, child_id| -> ControlFlow { if adjustments.is_empty() && let Node::Expr(e) = cx.tcx.hir_node(child_id) { adjustments = cx.typeck_results().expr_adjustments(e); } - if cx.tcx.hir().span(parent_id).ctxt() != ctxt { - return ControlFlow::Break(()); - } + same_ctxt &= cx.tcx.hir().span(parent_id).ctxt() == ctxt; if let Node::Expr(e) = parent { match e.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id != child_id => { @@ -2805,71 +2872,26 @@ pub fn expr_use_ctxt<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> Optio } } ControlFlow::Continue(()) - })? - .continue_value() - .map(|(use_node, child_id)| { - let node = match use_node { - Node::LetStmt(l) => ExprUseNode::LetStmt(l), - Node::ExprField(field) => ExprUseNode::Field(field), - - Node::Item(&Item { - kind: ItemKind::Static(..) | ItemKind::Const(..), - owner_id, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Const(..), - owner_id, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Const(..), - owner_id, - .. - }) => ExprUseNode::ConstStatic(owner_id), - - Node::Item(&Item { - kind: ItemKind::Fn(..), - owner_id, - .. - }) - | Node::TraitItem(&TraitItem { - kind: TraitItemKind::Fn(..), - owner_id, - .. - }) - | Node::ImplItem(&ImplItem { - kind: ImplItemKind::Fn(..), - owner_id, - .. - }) => ExprUseNode::Return(owner_id), - - Node::Expr(use_expr) => match use_expr.kind { - ExprKind::Ret(_) => ExprUseNode::Return(OwnerId { - def_id: cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), - }), - ExprKind::Closure(closure) => ExprUseNode::Return(OwnerId { def_id: closure.def_id }), - ExprKind::Call(func, args) => match args.iter().position(|arg| arg.hir_id == child_id) { - Some(i) => ExprUseNode::FnArg(func, i), - None => ExprUseNode::Callee, - }, - ExprKind::MethodCall(name, _, args, _) => ExprUseNode::MethodArg( - use_expr.hir_id, - name.args, - args.iter().position(|arg| arg.hir_id == child_id).map_or(0, |i| i + 1), - ), - ExprKind::Field(child, name) if child.hir_id == e.hir_id => ExprUseNode::FieldAccess(name), - _ => ExprUseNode::Expr, - }, - _ => ExprUseNode::Other, - }; - ExprUseCtxt { + }); + match node { + Some(ControlFlow::Continue((node, child_id))) => ExprUseCtxt { node, + child_id, adjustments, is_ty_unified, moved_before_use, - } - }) + same_ctxt, + }, + Some(ControlFlow::Break(_)) => unreachable!("type of node is ControlFlow"), + None => ExprUseCtxt { + node: Node::Crate(cx.tcx.hir().root_module()), + child_id: HirId::INVALID, + adjustments: &[], + is_ty_unified: true, + moved_before_use: true, + same_ctxt: false, + }, + } } /// Tokenizes the input while keeping the text associated with each token. diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 7b4fd8a210edf..07e6705cd3d95 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -69,7 +69,7 @@ impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) { let lhs = place.local; match rvalue { - mir::Rvalue::Ref(_, _, borrowed) => { + mir::Rvalue::Ref(_, _, borrowed) | mir::Rvalue::CopyForDeref(borrowed) => { self.possible_borrower.add(borrowed.local, lhs); }, other => { diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 9bf068ee3cdee..3f66813801dc0 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -110,5 +110,4 @@ pub const VEC_POP: [&str; 4] = ["alloc", "vec", "Vec", "pop"]; pub const WAKER: [&str; 4] = ["core", "task", "wake", "Waker"]; pub const OPTION_UNWRAP: [&str; 4] = ["core", "option", "Option", "unwrap"]; pub const OPTION_EXPECT: [&str; 4] = ["core", "option", "Option", "expect"]; -#[expect(clippy::invalid_paths)] // not sure why it thinks this, it works so pub const BOOL_THEN: [&str; 4] = ["core", "bool", "", "then"]; diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index e2e5cde947d83..496c8f5b55373 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -9,12 +9,16 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource}; use rustc_lint::{LateContext, LintContext}; use rustc_session::Session; use rustc_span::source_map::{original_sp, SourceMap}; -use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP}; +use rustc_span::{ + hygiene, BytePos, FileNameDisplayPreference, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, + DUMMY_SP, +}; use std::borrow::Cow; +use std::fmt; use std::ops::Range; -/// A type which can be converted to the range portion of a `Span`. -pub trait SpanRange { +/// Conversion of a value into the range portion of a `Span`. +pub trait SpanRange: Sized { fn into_range(self) -> Range; } impl SpanRange for Span { @@ -34,6 +38,182 @@ impl SpanRange for Range { } } +/// Conversion of a value into a `Span` +pub trait IntoSpan: Sized { + fn into_span(self) -> Span; + fn with_ctxt(self, ctxt: SyntaxContext) -> Span; +} +impl IntoSpan for Span { + fn into_span(self) -> Span { + self + } + fn with_ctxt(self, ctxt: SyntaxContext) -> Span { + self.with_ctxt(ctxt) + } +} +impl IntoSpan for SpanData { + fn into_span(self) -> Span { + self.span() + } + fn with_ctxt(self, ctxt: SyntaxContext) -> Span { + Span::new(self.lo, self.hi, ctxt, self.parent) + } +} +impl IntoSpan for Range { + fn into_span(self) -> Span { + Span::with_root_ctxt(self.start, self.end) + } + fn with_ctxt(self, ctxt: SyntaxContext) -> Span { + Span::new(self.start, self.end, ctxt, None) + } +} + +pub trait SpanRangeExt: SpanRange { + /// Gets the source file, and range in the file, of the given span. Returns `None` if the span + /// extends through multiple files, or is malformed. + fn get_source_text(self, cx: &impl LintContext) -> Option { + get_source_text(cx.sess().source_map(), self.into_range()) + } + + /// Calls the given function with the source text referenced and returns the value. Returns + /// `None` if the source text cannot be retrieved. + fn with_source_text(self, cx: &impl LintContext, f: impl for<'a> FnOnce(&'a str) -> T) -> Option { + with_source_text(cx.sess().source_map(), self.into_range(), f) + } + + /// Checks if the referenced source text satisfies the given predicate. Returns `false` if the + /// source text cannot be retrieved. + fn check_source_text(self, cx: &impl LintContext, pred: impl for<'a> FnOnce(&'a str) -> bool) -> bool { + self.with_source_text(cx, pred).unwrap_or(false) + } + + /// Calls the given function with the both the text of the source file and the referenced range, + /// and returns the value. Returns `None` if the source text cannot be retrieved. + fn with_source_text_and_range( + self, + cx: &impl LintContext, + f: impl for<'a> FnOnce(&'a str, Range) -> T, + ) -> Option { + with_source_text_and_range(cx.sess().source_map(), self.into_range(), f) + } + + /// Calls the given function with the both the text of the source file and the referenced range, + /// and creates a new span with the returned range. Returns `None` if the source text cannot be + /// retrieved, or no result is returned. + /// + /// The new range must reside within the same source file. + fn map_range( + self, + cx: &impl LintContext, + f: impl for<'a> FnOnce(&'a str, Range) -> Option>, + ) -> Option> { + map_range(cx.sess().source_map(), self.into_range(), f) + } + + /// Extends the range to include all preceding whitespace characters. + fn with_leading_whitespace(self, cx: &impl LintContext) -> Range { + with_leading_whitespace(cx.sess().source_map(), self.into_range()) + } + + /// Trims the leading whitespace from the range. + fn trim_start(self, cx: &impl LintContext) -> Range { + trim_start(cx.sess().source_map(), self.into_range()) + } + + /// Writes the referenced source text to the given writer. Will return `Err` if the source text + /// could not be retrieved. + fn write_source_text_to(self, cx: &impl LintContext, dst: &mut impl fmt::Write) -> fmt::Result { + write_source_text_to(cx.sess().source_map(), self.into_range(), dst) + } + + /// Extracts the referenced source text as an owned string. + fn source_text_to_string(self, cx: &impl LintContext) -> Option { + self.with_source_text(cx, ToOwned::to_owned) + } +} +impl SpanRangeExt for T {} + +fn get_source_text(sm: &SourceMap, sp: Range) -> Option { + let start = sm.lookup_byte_offset(sp.start); + let end = sm.lookup_byte_offset(sp.end); + if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos { + return None; + } + let range = start.pos.to_usize()..end.pos.to_usize(); + Some(SourceFileRange { sf: start.sf, range }) +} + +fn with_source_text(sm: &SourceMap, sp: Range, f: impl for<'a> FnOnce(&'a str) -> T) -> Option { + if let Some(src) = get_source_text(sm, sp) + && let Some(src) = src.as_str() + { + Some(f(src)) + } else { + None + } +} + +fn with_source_text_and_range( + sm: &SourceMap, + sp: Range, + f: impl for<'a> FnOnce(&'a str, Range) -> T, +) -> Option { + if let Some(src) = get_source_text(sm, sp) + && let Some(text) = &src.sf.src + { + Some(f(text, src.range)) + } else { + None + } +} + +#[expect(clippy::cast_possible_truncation)] +fn map_range( + sm: &SourceMap, + sp: Range, + f: impl for<'a> FnOnce(&'a str, Range) -> Option>, +) -> Option> { + if let Some(src) = get_source_text(sm, sp.clone()) + && let Some(text) = &src.sf.src + && let Some(range) = f(text, src.range.clone()) + { + debug_assert!( + range.start <= text.len() && range.end <= text.len(), + "Range `{range:?}` is outside the source file (file `{}`, length `{}`)", + src.sf.name.display(FileNameDisplayPreference::Local), + text.len(), + ); + debug_assert!(range.start <= range.end, "Range `{range:?}` has overlapping bounds"); + let dstart = (range.start as u32).wrapping_sub(src.range.start as u32); + let dend = (range.end as u32).wrapping_sub(src.range.start as u32); + Some(BytePos(sp.start.0.wrapping_add(dstart))..BytePos(sp.start.0.wrapping_add(dend))) + } else { + None + } +} + +fn with_leading_whitespace(sm: &SourceMap, sp: Range) -> Range { + map_range(sm, sp.clone(), |src, range| { + Some(src.get(..range.start)?.trim_end().len()..range.end) + }) + .unwrap_or(sp) +} + +fn trim_start(sm: &SourceMap, sp: Range) -> Range { + map_range(sm, sp.clone(), |src, range| { + let src = src.get(range.clone())?; + Some(range.start + (src.len() - src.trim_start().len())..range.end) + }) + .unwrap_or(sp) +} + +fn write_source_text_to(sm: &SourceMap, sp: Range, dst: &mut impl fmt::Write) -> fmt::Result { + match with_source_text(sm, sp, |src| dst.write_str(src)) { + Some(x) => x, + None => Err(fmt::Error), + } +} + pub struct SourceFileRange { pub sf: Lrc, pub range: Range, @@ -46,21 +226,6 @@ impl SourceFileRange { } } -/// Gets the source file, and range in the file, of the given span. Returns `None` if the span -/// extends through multiple files, or is malformed. -pub fn get_source_text(cx: &impl LintContext, sp: impl SpanRange) -> Option { - fn f(sm: &SourceMap, sp: Range) -> Option { - let start = sm.lookup_byte_offset(sp.start); - let end = sm.lookup_byte_offset(sp.end); - if !Lrc::ptr_eq(&start.sf, &end.sf) || start.pos > end.pos { - return None; - } - let range = start.pos.to_usize()..end.pos.to_usize(); - Some(SourceFileRange { sf: start.sf, range }) - } - f(cx.sess().source_map(), sp.into_range()) -} - /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`. pub fn expr_block( cx: &T, diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 3790a852f7edb..e5d2056419681 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -1349,3 +1349,17 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n None } } + +/// Get's the type of a field by name. +pub fn get_field_by_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> Option> { + match *ty.kind() { + ty::Adt(def, args) if def.is_union() || def.is_struct() => def + .non_enum_variant() + .fields + .iter() + .find(|f| f.name == name) + .map(|f| f.ty(tcx, args)), + ty::Tuple(args) => name.as_str().parse::().ok().and_then(|i| args.get(i).copied()), + _ => None, + } +} diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 8c5a409e25b15..ae9e77b8eed05 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -11,28 +11,19 @@ publish = false default-run = "lintcheck" [dependencies] -anyhow = "1.0.69" cargo_metadata = "0.15.3" clap = { version = "4.4", features = ["derive", "env"] } -crates_io_api = "0.8.1" crossbeam-channel = "0.5.6" +diff = "0.1.13" flate2 = "1.0" -indicatif = "0.17.3" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" +strip-ansi-escapes = "0.2.0" tar = "0.4" toml = "0.7.3" -ureq = "2.2" +ureq = { version = "2.2", features = ["json"] } walkdir = "2.3" [features] deny-warnings = [] - -[[bin]] -name = "lintcheck" -path = "src/main.rs" - -[[bin]] -name = "popular-crates" -path = "src/popular-crates.rs" diff --git a/lintcheck/README.md b/lintcheck/README.md index 61b581ba0faec..2d6039caeef07 100644 --- a/lintcheck/README.md +++ b/lintcheck/README.md @@ -26,11 +26,11 @@ the repo root. The results will then be saved to `lintcheck-logs/custom_logs.toml`. The `custom.toml` file may be built using recently most -downloaded crates by using the `popular-crates` binary from the `lintcheck` -directory. For example, to retrieve the 100 recently most downloaded crates: +downloaded crates by using `cargo lintcheck popular`. For example, to retrieve +the 200 recently most downloaded crates: ``` -cargo run --release --bin popular-crates -- -n 100 custom.toml +cargo lintcheck popular -n 200 custom.toml ``` diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index 3f712f453fa0a..e6cd7c9fdc2bf 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -1,8 +1,9 @@ -use clap::Parser; +use clap::{Parser, Subcommand, ValueEnum}; use std::num::NonZero; use std::path::PathBuf; -#[derive(Clone, Debug, Parser)] +#[derive(Parser, Clone, Debug)] +#[command(args_conflicts_with_subcommands = true)] pub(crate) struct LintcheckConfig { /// Number of threads to use (default: all unless --fix or --recursive) #[clap( @@ -35,12 +36,45 @@ pub(crate) struct LintcheckConfig { /// Apply a filter to only collect specified lints, this also overrides `allow` attributes #[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)] pub lint_filter: Vec, - /// Change the reports table to use markdown links - #[clap(long)] - pub markdown: bool, + /// Set the output format of the log file + #[clap(long, short, default_value = "text")] + pub format: OutputFormat, /// Run clippy on the dependencies of crates specified in crates-toml #[clap(long, conflicts_with("max_jobs"))] pub recursive: bool, + #[command(subcommand)] + pub subcommand: Option, +} + +#[derive(Subcommand, Clone, Debug)] +pub(crate) enum Commands { + /// Display a markdown diff between two lintcheck log files in JSON format + Diff { old: PathBuf, new: PathBuf }, + /// Create a lintcheck crates TOML file containing the top N popular crates + Popular { + /// Output TOML file name + output: PathBuf, + /// Number of crate names to download + #[clap(short, long, default_value_t = 100)] + number: usize, + }, +} + +#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum OutputFormat { + Text, + Markdown, + Json, +} + +impl OutputFormat { + fn file_extension(self) -> &'static str { + match self { + OutputFormat::Text => "txt", + OutputFormat::Markdown => "md", + OutputFormat::Json => "json", + } + } } impl LintcheckConfig { @@ -53,7 +87,7 @@ impl LintcheckConfig { config.lintcheck_results_path = PathBuf::from(format!( "lintcheck-logs/{}_logs.{}", filename.display(), - if config.markdown { "md" } else { "txt" } + config.format.file_extension(), )); // look at the --threads arg, if 0 is passed, use the threads count diff --git a/lintcheck/src/json.rs b/lintcheck/src/json.rs new file mode 100644 index 0000000000000..43d0413c7ceeb --- /dev/null +++ b/lintcheck/src/json.rs @@ -0,0 +1,122 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::fs; +use std::hash::Hash; +use std::path::Path; + +use crate::ClippyWarning; + +/// Creates the log file output for [`crate::config::OutputFormat::Json`] +pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String { + serde_json::to_string(&clippy_warnings).unwrap() +} + +fn load_warnings(path: &Path) -> Vec { + let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); + + serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display())) +} + +/// Group warnings by their primary span location + lint name +fn create_map(warnings: &[ClippyWarning]) -> HashMap> { + let mut map = HashMap::<_, Vec<_>>::with_capacity(warnings.len()); + + for warning in warnings { + let span = warning.span(); + let key = (&warning.lint_type, &span.file_name, span.byte_start, span.byte_end); + + map.entry(key).or_default().push(warning); + } + + map +} + +fn print_warnings(title: &str, warnings: &[&ClippyWarning]) { + if warnings.is_empty() { + return; + } + + println!("### {title}"); + println!("```"); + for warning in warnings { + print!("{}", warning.diag); + } + println!("```"); +} + +fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) { + fn render(warnings: &[&ClippyWarning]) -> String { + let mut rendered = String::new(); + for warning in warnings { + write!(&mut rendered, "{}", warning.diag).unwrap(); + } + rendered + } + + if changed.is_empty() { + return; + } + + println!("### Changed"); + println!("```diff"); + for &(old, new) in changed { + let old_rendered = render(old); + let new_rendered = render(new); + + for change in diff::lines(&old_rendered, &new_rendered) { + use diff::Result::{Both, Left, Right}; + + match change { + Both(unchanged, _) => { + println!(" {unchanged}"); + }, + Left(removed) => { + println!("-{removed}"); + }, + Right(added) => { + println!("+{added}"); + }, + } + } + } + println!("```"); +} + +pub(crate) fn diff(old_path: &Path, new_path: &Path) { + let old_warnings = load_warnings(old_path); + let new_warnings = load_warnings(new_path); + + let old_map = create_map(&old_warnings); + let new_map = create_map(&new_warnings); + + let mut added = Vec::new(); + let mut removed = Vec::new(); + let mut changed = Vec::new(); + + for (key, new) in &new_map { + if let Some(old) = old_map.get(key) { + if old != new { + changed.push((old.as_slice(), new.as_slice())); + } + } else { + added.extend(new); + } + } + + for (key, old) in &old_map { + if !new_map.contains_key(key) { + removed.extend(old); + } + } + + print!( + "{} added, {} removed, {} changed\n\n", + added.len(), + removed.len(), + changed.len() + ); + + print_warnings("Added", &added); + print_warnings("Removed", &removed); + print_changed_diff(&changed); +} diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index deb06094d83bd..ec72e0eb5dcb5 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -12,26 +12,29 @@ unused_lifetimes, unused_qualifications )] -#![allow(clippy::collapsible_else_if)] +#![allow(clippy::collapsible_else_if, clippy::needless_borrows_for_generic_args)] mod config; mod driver; +mod json; +mod popular_crates; mod recursive; -use crate::config::LintcheckConfig; +use crate::config::{Commands, LintcheckConfig, OutputFormat}; use crate::recursive::LintcheckServer; use std::collections::{HashMap, HashSet}; use std::env::consts::EXE_SUFFIX; -use std::fmt::{self, Write as _}; +use std::fmt::{self, Display, Write as _}; +use std::hash::Hash; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; -use std::process::{Command, ExitStatus}; +use std::process::{Command, ExitStatus, Stdio}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::Duration; use std::{env, fs, thread}; -use cargo_metadata::diagnostic::Diagnostic; +use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan}; use cargo_metadata::Message; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -41,21 +44,21 @@ const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; const LINTCHECK_SOURCES: &str = "target/lintcheck/sources"; /// List of sources to check, loaded from a .toml file -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] struct SourceList { crates: HashMap, #[serde(default)] recursive: RecursiveOptions, } -#[derive(Debug, Serialize, Deserialize, Default)] +#[derive(Debug, Deserialize, Default)] struct RecursiveOptions { ignore: HashSet, } /// A crate source stored inside the .toml /// will be translated into on one of the `CrateSource` variants -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize)] struct TomlCrate { name: String, versions: Option>, @@ -67,7 +70,7 @@ struct TomlCrate { /// Represents an archive we download from crates.io, or a git repo, or a local repo/folder /// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` -#[derive(Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] enum CrateSource { CratesIo { name: String, @@ -111,6 +114,17 @@ struct RustcIce { pub crate_name: String, pub ice_content: String, } + +impl Display for RustcIce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:\n{}\n========================================\n", + self.crate_name, self.ice_content + ) + } +} + impl RustcIce { pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option { if status.code().unwrap_or(0) == 101 @@ -127,60 +141,58 @@ impl RustcIce { } /// A single warning that clippy issued while checking a `Crate` -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] struct ClippyWarning { - file: String, - line: usize, - column: usize, + crate_name: String, + crate_version: String, lint_type: String, - message: String, + diag: Diagnostic, } #[allow(unused)] impl ClippyWarning { - fn new(diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option { - let lint_type = diag.code?.code; + fn new(mut diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option { + let lint_type = diag.code.clone()?.code; if !(lint_type.contains("clippy") || diag.message.contains("clippy")) || diag.message.contains("could not read cargo metadata") { return None; } - let span = diag.spans.into_iter().find(|span| span.is_primary)?; - - let file = if let Ok(stripped) = Path::new(&span.file_name).strip_prefix(env!("CARGO_HOME")) { - format!("$CARGO_HOME/{}", stripped.display()) - } else { - format!( - "target/lintcheck/sources/{crate_name}-{crate_version}/{}", - span.file_name - ) - }; + // --recursive bypasses cargo so we have to strip the rendered output ourselves + let rendered = diag.rendered.as_mut().unwrap(); + *rendered = strip_ansi_escapes::strip_str(&rendered); Some(Self { - file, - line: span.line_start, - column: span.column_start, + crate_name: crate_name.to_owned(), + crate_version: crate_version.to_owned(), lint_type, - message: diag.message, + diag, }) } - fn to_output(&self, markdown: bool) -> String { - let file_with_pos = format!("{}:{}:{}", &self.file, &self.line, &self.column); - if markdown { - let mut file = self.file.clone(); - if !file.starts_with('$') { - file.insert_str(0, "../"); - } + fn span(&self) -> &DiagnosticSpan { + self.diag.spans.iter().find(|span| span.is_primary).unwrap() + } - let mut output = String::from("| "); - let _: fmt::Result = write!(output, "[`{file_with_pos}`]({file}#L{})", self.line); - let _: fmt::Result = write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.message); - output.push('\n'); - output - } else { - format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.message) + fn to_output(&self, format: OutputFormat) -> String { + let span = self.span(); + let mut file = span.file_name.clone(); + let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end); + match format { + OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.diag.message), + OutputFormat::Markdown => { + if file.starts_with("target") { + file.insert_str(0, "../"); + } + + let mut output = String::from("| "); + write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap(); + write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.diag.message).unwrap(); + output.push('\n'); + output + }, + OutputFormat::Json => unreachable!("JSON output is handled via serde"), } } } @@ -333,10 +345,9 @@ impl CrateSource { impl Crate { /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy /// issued - #[allow(clippy::too_many_arguments)] + #[allow(clippy::too_many_arguments, clippy::too_many_lines)] fn run_clippy_lints( &self, - cargo_clippy_path: &Path, clippy_driver_path: &Path, target_dir_index: &AtomicUsize, total_crates_to_lint: usize, @@ -362,17 +373,27 @@ impl Crate { ); } - let cargo_clippy_path = fs::canonicalize(cargo_clippy_path).unwrap(); - let shared_target_dir = clippy_project_root().join("target/lintcheck/shared_target_dir"); - let mut cargo_clippy_args = if config.fix { - vec!["--quiet", "--fix", "--"] - } else { - vec!["--quiet", "--message-format=json", "--"] - }; + let cargo_home = env!("CARGO_HOME"); + + // `src/lib.rs` -> `target/lintcheck/sources/crate-1.2.3/src/lib.rs` + let remap_relative = format!("={}", self.path.display()); + // Fallback for other sources, `~/.cargo/...` -> `$CARGO_HOME/...` + let remap_cargo_home = format!("{cargo_home}=$CARGO_HOME"); + // `~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/crate-2.3.4/src/lib.rs` + // -> `crate-2.3.4/src/lib.rs` + let remap_crates_io = format!("{cargo_home}/registry/src/index.crates.io-6f17d22bba15001f/="); + + let mut clippy_args = vec![ + "--remap-path-prefix", + &remap_relative, + "--remap-path-prefix", + &remap_cargo_home, + "--remap-path-prefix", + &remap_crates_io, + ]; - let mut clippy_args = Vec::<&str>::new(); if let Some(options) = &self.options { for opt in options { clippy_args.push(opt); @@ -388,23 +409,23 @@ impl Crate { clippy_args.extend(lint_filter.iter().map(String::as_str)); } - if let Some(server) = server { - let target = shared_target_dir.join("recursive"); + let mut cmd = Command::new("cargo"); + cmd.arg(if config.fix { "fix" } else { "check" }) + .arg("--quiet") + .current_dir(&self.path) + .env("CLIPPY_ARGS", clippy_args.join("__CLIPPY_HACKERY__")); + if let Some(server) = server { // `cargo clippy` is a wrapper around `cargo check` that mainly sets `RUSTC_WORKSPACE_WRAPPER` to // `clippy-driver`. We do the same thing here with a couple changes: // // `RUSTC_WRAPPER` is used instead of `RUSTC_WORKSPACE_WRAPPER` so that we can lint all crate // dependencies rather than only workspace members // - // The wrapper is set to the `lintcheck` so we can force enable linting and ignore certain crates + // The wrapper is set to `lintcheck` itself so we can force enable linting and ignore certain crates // (see `crate::driver`) - let status = Command::new(env::var("CARGO").unwrap_or("cargo".into())) - .arg("check") - .arg("--quiet") - .current_dir(&self.path) - .env("CLIPPY_ARGS", clippy_args.join("__CLIPPY_HACKERY__")) - .env("CARGO_TARGET_DIR", target) + let status = cmd + .env("CARGO_TARGET_DIR", shared_target_dir.join("recursive")) .env("RUSTC_WRAPPER", env::current_exe().unwrap()) // Pass the absolute path so `crate::driver` can find `clippy-driver`, as it's executed in various // different working directories @@ -416,23 +437,19 @@ impl Crate { assert_eq!(status.code(), Some(0)); return Vec::new(); - } + }; - cargo_clippy_args.extend(clippy_args); + if !config.fix { + cmd.arg("--message-format=json"); + } - let all_output = Command::new(&cargo_clippy_path) + let all_output = cmd // use the looping index to create individual target dirs .env("CARGO_TARGET_DIR", shared_target_dir.join(format!("_{thread_index:?}"))) - .args(&cargo_clippy_args) - .current_dir(&self.path) + // Roughly equivalent to `cargo clippy`/`cargo clippy --fix` + .env("RUSTC_WORKSPACE_WRAPPER", clippy_driver_path) .output() - .unwrap_or_else(|error| { - panic!( - "Encountered error:\n{error:?}\ncargo_clippy_path: {}\ncrate path:{}\n", - &cargo_clippy_path.display(), - &self.path.display() - ); - }); + .unwrap(); let stdout = String::from_utf8_lossy(&all_output.stdout); let stderr = String::from_utf8_lossy(&all_output.stderr); let status = &all_output.status; @@ -479,15 +496,17 @@ impl Crate { } /// Builds clippy inside the repo to make sure we have a clippy executable we can use. -fn build_clippy() { - let status = Command::new(env::var("CARGO").unwrap_or("cargo".into())) - .arg("build") - .status() - .expect("Failed to build clippy!"); - if !status.success() { +fn build_clippy() -> String { + let output = Command::new("cargo") + .args(["run", "--bin=clippy-driver", "--", "--version"]) + .stderr(Stdio::inherit()) + .output() + .unwrap(); + if !output.status.success() { eprintln!("Error: Failed to compile Clippy!"); std::process::exit(1); } + String::from_utf8_lossy(&output.stdout).into_owned() } /// Read a `lintcheck_crates.toml` file @@ -554,10 +573,10 @@ fn read_crates(toml_path: &Path) -> (Vec, RecursiveOptions) { } /// Generate a short list of occurring lints-types and their count -fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { +fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { // count lint type occurrences let mut counter: HashMap<&String, usize> = HashMap::new(); - clippy_warnings + warnings .iter() .for_each(|wrn| *counter.entry(&wrn.lint_type).or_insert(0) += 1); @@ -580,7 +599,6 @@ fn gather_stats(clippy_warnings: &[ClippyWarning]) -> (String, HashMap<&String, (stats_string, counter) } -#[allow(clippy::too_many_lines)] fn main() { // We're being executed as a `RUSTC_WRAPPER` as part of `--recursive` if let Ok(addr) = env::var("LINTCHECK_SERVER") { @@ -595,31 +613,29 @@ fn main() { let config = LintcheckConfig::new(); - println!("Compiling clippy..."); - build_clippy(); - println!("Done compiling"); + match config.subcommand { + Some(Commands::Diff { old, new }) => json::diff(&old, &new), + Some(Commands::Popular { output, number }) => popular_crates::fetch(output, number).unwrap(), + None => lintcheck(config), + } +} - let cargo_clippy_path = fs::canonicalize(format!("target/debug/cargo-clippy{EXE_SUFFIX}")).unwrap(); +#[allow(clippy::too_many_lines)] +fn lintcheck(config: LintcheckConfig) { + let clippy_ver = build_clippy(); let clippy_driver_path = fs::canonicalize(format!("target/debug/clippy-driver{EXE_SUFFIX}")).unwrap(); // assert that clippy is found assert!( - cargo_clippy_path.is_file(), - "target/debug/cargo-clippy binary not found! {}", - cargo_clippy_path.display() + clippy_driver_path.is_file(), + "target/debug/clippy-driver binary not found! {}", + clippy_driver_path.display() ); - let clippy_ver = Command::new(&cargo_clippy_path) - .arg("--version") - .output() - .map(|o| String::from_utf8_lossy(&o.stdout).into_owned()) - .expect("could not get clippy version!"); - // download and extract the crates, then run clippy on them and collect clippy's warnings // flatten into one big list of warnings let (crates, recursive_options) = read_crates(&config.sources_toml_path); - let old_stats = read_stats_from_file(&config.lintcheck_results_path); let counter = AtomicUsize::new(1); let lint_filter: Vec = config @@ -678,7 +694,6 @@ fn main() { .par_iter() .flat_map(|krate| { krate.run_clippy_lints( - &cargo_clippy_path, &clippy_driver_path, &counter, crates.len(), @@ -711,39 +726,51 @@ fn main() { } } + let text = match config.format { + OutputFormat::Text | OutputFormat::Markdown => output(&warnings, &raw_ices, clippy_ver, &config), + OutputFormat::Json => { + if !raw_ices.is_empty() { + for ice in raw_ices { + println!("{ice}"); + } + panic!("Some crates ICEd"); + } + + json::output(&warnings) + }, + }; + + println!("Writing logs to {}", config.lintcheck_results_path.display()); + fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap(); + fs::write(&config.lintcheck_results_path, text).unwrap(); +} + +/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`] +fn output(warnings: &[ClippyWarning], ices: &[RustcIce], clippy_ver: String, config: &LintcheckConfig) -> String { // generate some stats - let (stats_formatted, new_stats) = gather_stats(&warnings); + let (stats_formatted, new_stats) = gather_stats(warnings); + let old_stats = read_stats_from_file(&config.lintcheck_results_path); - let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.markdown)).collect(); + let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.format)).collect(); all_msgs.sort(); all_msgs.push("\n\n### Stats:\n\n".into()); all_msgs.push(stats_formatted); - // save the text into lintcheck-logs/logs.txt let mut text = clippy_ver; // clippy version number on top text.push_str("\n### Reports\n\n"); - if config.markdown { + if config.format == OutputFormat::Markdown { text.push_str("| file | lint | message |\n"); text.push_str("| --- | --- | --- |\n"); } write!(text, "{}", all_msgs.join("")).unwrap(); text.push_str("\n\n### ICEs:\n"); - for ice in &raw_ices { - let _: fmt::Result = write!( - text, - "{}:\n{}\n========================================\n\n", - ice.crate_name, ice.ice_content - ); + for ice in ices { + writeln!(text, "{ice}").unwrap(); } - println!("Writing logs to {}", config.lintcheck_results_path.display()); - if !raw_ices.is_empty() { - println!("WARNING: at least one ICE reported, check log file"); - } - fs::create_dir_all(config.lintcheck_results_path.parent().unwrap()).unwrap(); - fs::write(&config.lintcheck_results_path, text).unwrap(); - print_stats(old_stats, new_stats, &config.lint_filter); + + text } /// read the previous stats from the lintcheck-log file @@ -865,7 +892,7 @@ fn lintcheck_test() { "--crates-toml", "lintcheck/test_sources.toml", ]; - let status = Command::new(env::var("CARGO").unwrap_or("cargo".into())) + let status = Command::new("cargo") .args(args) .current_dir("..") // repo root .status(); diff --git a/lintcheck/src/popular-crates.rs b/lintcheck/src/popular-crates.rs deleted file mode 100644 index fdab984ad86cb..0000000000000 --- a/lintcheck/src/popular-crates.rs +++ /dev/null @@ -1,65 +0,0 @@ -#![deny(clippy::pedantic)] - -use clap::Parser; -use crates_io_api::{CratesQueryBuilder, Sort, SyncClient}; -use indicatif::ProgressBar; -use std::collections::HashSet; -use std::fs::File; -use std::io::{BufWriter, Write}; -use std::path::PathBuf; -use std::time::Duration; - -#[derive(Parser)] -struct Opts { - /// Output TOML file name - output: PathBuf, - /// Number of crate names to download - #[clap(short, long, default_value_t = 100)] - number: usize, - /// Do not output progress - #[clap(short, long)] - quiet: bool, -} - -fn main() -> anyhow::Result<()> { - let opts = Opts::parse(); - let mut output = BufWriter::new(File::create(opts.output)?); - output.write_all(b"[crates]\n")?; - let client = SyncClient::new( - "clippy/lintcheck (github.com/rust-lang/rust-clippy/)", - Duration::from_secs(1), - )?; - let mut seen_crates = HashSet::new(); - let pb = if opts.quiet { - None - } else { - Some(ProgressBar::new(opts.number as u64)) - }; - let mut query = CratesQueryBuilder::new() - .sort(Sort::RecentDownloads) - .page_size(100) - .build(); - while seen_crates.len() < opts.number { - let retrieved = client.crates(query.clone())?.crates; - if retrieved.is_empty() { - eprintln!("No more than {} crates available from API", seen_crates.len()); - break; - } - for c in retrieved { - if seen_crates.insert(c.name.clone()) { - output.write_all( - format!( - "{} = {{ name = '{}', versions = ['{}'] }}\n", - c.name, c.name, c.max_version - ) - .as_bytes(), - )?; - if let Some(pb) = &pb { - pb.inc(1); - } - } - } - query.set_page(query.page() + 1); - } - Ok(()) -} diff --git a/lintcheck/src/popular_crates.rs b/lintcheck/src/popular_crates.rs new file mode 100644 index 0000000000000..880a8bd81f083 --- /dev/null +++ b/lintcheck/src/popular_crates.rs @@ -0,0 +1,52 @@ +use serde::Deserialize; +use std::error::Error; +use std::fmt::Write; +use std::fs; +use std::path::PathBuf; + +#[derive(Deserialize, Debug)] +struct Page { + crates: Vec, + meta: Meta, +} + +#[derive(Deserialize, Debug)] +struct Crate { + name: String, + max_version: String, +} + +#[derive(Deserialize, Debug)] +struct Meta { + next_page: String, +} + +pub(crate) fn fetch(output: PathBuf, number: usize) -> Result<(), Box> { + let agent = ureq::builder() + .user_agent("clippy/lintcheck (github.com/rust-lang/rust-clippy/)") + .build(); + + let mut crates = Vec::with_capacity(number); + let mut query = "?sort=recent-downloads&per_page=100".to_string(); + while crates.len() < number { + let page: Page = agent + .get(&format!("https://crates.io/api/v1/crates{query}")) + .call()? + .into_json()?; + + query = page.meta.next_page; + crates.extend(page.crates); + crates.truncate(number); + + let width = number.ilog10() as usize + 1; + println!("Fetched {:>width$}/{number} crates", crates.len()); + } + + let mut out = "[crates]\n".to_string(); + for Crate { name, max_version } in crates { + writeln!(out, "{name} = {{ name = '{name}', versions = ['{max_version}'] }}").unwrap(); + } + fs::write(output, out)?; + + Ok(()) +} diff --git a/rust-toolchain b/rust-toolchain index 842c2f3de0d10..72b50d59f7e9a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-06-13" +channel = "nightly-2024-06-27" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/integration.rs b/tests/integration.rs index 7f4500826ff6c..19c5f3a41339e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -55,6 +55,7 @@ fn integration_test() { "clippy", "--all-targets", "--all-features", + "--message-format=short", "--", "--cap-lints", "warn", diff --git a/tests/ui-cargo/duplicate_mod/fail/Cargo.stderr b/tests/ui-cargo/duplicate_mod/fail/Cargo.stderr index 912ea9bb4d111..b626551e35b93 100644 --- a/tests/ui-cargo/duplicate_mod/fail/Cargo.stderr +++ b/tests/ui-cargo/duplicate_mod/fail/Cargo.stderr @@ -1,10 +1,10 @@ error: file is loaded as a module multiple times: `src/b.rs` - --> src/main.rs:5:1 + --> src/main.rs:3:1 | -5 | mod b; +3 | mod b; | ^^^^^^ first loaded here -6 | / #[path = "b.rs"] -7 | | mod b2; +4 | / #[path = "b.rs"] +5 | | mod b2; | |_______^ loaded again here | = help: replace all but one `mod` item with `use` items @@ -12,34 +12,34 @@ error: file is loaded as a module multiple times: `src/b.rs` = help: to override `-D warnings` add `#[allow(clippy::duplicate_mod)]` error: file is loaded as a module multiple times: `src/c.rs` - --> src/main.rs:9:1 + --> src/main.rs:7:1 | -9 | mod c; +7 | mod c; | ^^^^^^ first loaded here -10 | / #[path = "c.rs"] -11 | | mod c2; +8 | / #[path = "c.rs"] +9 | | mod c2; | |_______^ loaded again here -12 | / #[path = "c.rs"] -13 | | mod c3; +10 | / #[path = "c.rs"] +11 | | mod c3; | |_______^ loaded again here | = help: replace all but one `mod` item with `use` items error: file is loaded as a module multiple times: `src/d.rs` - --> src/main.rs:18:1 + --> src/main.rs:16:1 | -18 | mod d; +16 | mod d; | ^^^^^^ first loaded here -19 | / #[path = "d.rs"] -20 | | mod d2; +17 | / #[path = "d.rs"] +18 | | mod d2; | |_______^ loaded again here | = help: replace all but one `mod` item with `use` items error: file is loaded as a module multiple times: `src/from_other_module.rs` - --> src/main.rs:15:1 + --> src/main.rs:13:1 | -15 | mod from_other_module; +13 | mod from_other_module; | ^^^^^^^^^^^^^^^^^^^^^^ first loaded here | ::: src/other_module/mod.rs:1:1 diff --git a/tests/ui-cargo/duplicate_mod/fail/src/main.rs b/tests/ui-cargo/duplicate_mod/fail/src/main.rs index a99fe2e12bb27..9cbe4b83b25e0 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/main.rs +++ b/tests/ui-cargo/duplicate_mod/fail/src/main.rs @@ -1,4 +1,3 @@ - mod a; mod b; diff --git a/tests/ui/assigning_clones.fixed b/tests/ui/assigning_clones.fixed index 70ab43b49b3a7..60f6a385fc506 100644 --- a/tests/ui/assigning_clones.fixed +++ b/tests/ui/assigning_clones.fixed @@ -272,3 +272,59 @@ impl<'a> Add for &'a mut HasCloneFrom { self } } + +mod borrowck_conflicts { + //! Cases where clone_into and friends cannot be used because the src/dest have conflicting + //! borrows. + use std::path::PathBuf; + + fn issue12444(mut name: String) { + let parts = name.split(", ").collect::>(); + let first = *parts.first().unwrap(); + name = first.to_owned(); + } + + fn issue12444_simple() { + let mut s = String::new(); + let s2 = &s; + s = s2.to_owned(); + } + + fn issue12444_nodrop_projections() { + struct NoDrop; + + impl Clone for NoDrop { + fn clone(&self) -> Self { + todo!() + } + fn clone_from(&mut self, other: &Self) { + todo!() + } + } + + let mut s = NoDrop; + let s2 = &s; + s = s2.clone(); + + let mut s = (NoDrop, NoDrop); + let s2 = &s.0; + s.0 = s2.clone(); + + // This *could* emit a warning, but PossibleBorrowerMap only works with locals so it + // considers `s` fully borrowed + let mut s = (NoDrop, NoDrop); + let s2 = &s.1; + s.0 = s2.clone(); + } + + fn issue12460(mut name: String) { + if let Some(stripped_name) = name.strip_prefix("baz-") { + name = stripped_name.to_owned(); + } + } + + fn issue12749() { + let mut path = PathBuf::from("/a/b/c"); + path = path.components().as_path().to_owned(); + } +} diff --git a/tests/ui/assigning_clones.rs b/tests/ui/assigning_clones.rs index 9699fed100c8f..6eb85be511a89 100644 --- a/tests/ui/assigning_clones.rs +++ b/tests/ui/assigning_clones.rs @@ -272,3 +272,59 @@ impl<'a> Add for &'a mut HasCloneFrom { self } } + +mod borrowck_conflicts { + //! Cases where clone_into and friends cannot be used because the src/dest have conflicting + //! borrows. + use std::path::PathBuf; + + fn issue12444(mut name: String) { + let parts = name.split(", ").collect::>(); + let first = *parts.first().unwrap(); + name = first.to_owned(); + } + + fn issue12444_simple() { + let mut s = String::new(); + let s2 = &s; + s = s2.to_owned(); + } + + fn issue12444_nodrop_projections() { + struct NoDrop; + + impl Clone for NoDrop { + fn clone(&self) -> Self { + todo!() + } + fn clone_from(&mut self, other: &Self) { + todo!() + } + } + + let mut s = NoDrop; + let s2 = &s; + s = s2.clone(); + + let mut s = (NoDrop, NoDrop); + let s2 = &s.0; + s.0 = s2.clone(); + + // This *could* emit a warning, but PossibleBorrowerMap only works with locals so it + // considers `s` fully borrowed + let mut s = (NoDrop, NoDrop); + let s2 = &s.1; + s.0 = s2.clone(); + } + + fn issue12460(mut name: String) { + if let Some(stripped_name) = name.strip_prefix("baz-") { + name = stripped_name.to_owned(); + } + } + + fn issue12749() { + let mut path = PathBuf::from("/a/b/c"); + path = path.components().as_path().to_owned(); + } +} diff --git a/tests/ui/auxiliary/external_consts.rs b/tests/ui/auxiliary/external_consts.rs new file mode 100644 index 0000000000000..1885ba341f51e --- /dev/null +++ b/tests/ui/auxiliary/external_consts.rs @@ -0,0 +1 @@ +pub const MAGIC_NUMBER: i32 = 1; diff --git a/tests/ui/cast.rs b/tests/ui/cast.rs index c39f65a43e39c..146b04ab8131d 100644 --- a/tests/ui/cast.rs +++ b/tests/ui/cast.rs @@ -12,6 +12,7 @@ #![allow( clippy::cast_abs_to_unsigned, clippy::no_effect, + clippy::unnecessary_min_or_max, clippy::unnecessary_operation, clippy::unnecessary_literal_unwrap, clippy::identity_op diff --git a/tests/ui/cast.stderr b/tests/ui/cast.stderr index 452482fc88e28..7824bdfac25ef 100644 --- a/tests/ui/cast.stderr +++ b/tests/ui/cast.stderr @@ -1,5 +1,5 @@ error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:25:5 + --> tests/ui/cast.rs:26:5 | LL | x0 as f32; | ^^^^^^^^^ @@ -8,37 +8,37 @@ LL | x0 as f32; = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` error: casting `i64` to `f32` causes a loss of precision (`i64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:29:5 + --> tests/ui/cast.rs:30:5 | LL | x1 as f32; | ^^^^^^^^^ error: casting `i64` to `f64` causes a loss of precision (`i64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast.rs:31:5 + --> tests/ui/cast.rs:32:5 | LL | x1 as f64; | ^^^^^^^^^ error: casting `u32` to `f32` causes a loss of precision (`u32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:34:5 + --> tests/ui/cast.rs:35:5 | LL | x2 as f32; | ^^^^^^^^^ error: casting `u64` to `f32` causes a loss of precision (`u64` is 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast.rs:37:5 + --> tests/ui/cast.rs:38:5 | LL | x3 as f32; | ^^^^^^^^^ error: casting `u64` to `f64` causes a loss of precision (`u64` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast.rs:39:5 + --> tests/ui/cast.rs:40:5 | LL | x3 as f64; | ^^^^^^^^^ error: casting `f32` to `i32` may truncate the value - --> tests/ui/cast.rs:42:5 + --> tests/ui/cast.rs:43:5 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | 1f32 as i32; = help: to override `-D warnings` add `#[allow(clippy::cast_possible_truncation)]` error: casting `f32` to `u32` may truncate the value - --> tests/ui/cast.rs:44:5 + --> tests/ui/cast.rs:45:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | 1f32 as u32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:44:5 + --> tests/ui/cast.rs:45:5 | LL | 1f32 as u32; | ^^^^^^^^^^^ @@ -65,7 +65,7 @@ LL | 1f32 as u32; = help: to override `-D warnings` add `#[allow(clippy::cast_sign_loss)]` error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:48:5 + --> tests/ui/cast.rs:49:5 | LL | 1f64 as f32; | ^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL | 1f64 as f32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `i32` to `i8` may truncate the value - --> tests/ui/cast.rs:50:5 + --> tests/ui/cast.rs:51:5 | LL | 1i32 as i8; | ^^^^^^^^^^ @@ -85,7 +85,7 @@ LL | i8::try_from(1i32); | ~~~~~~~~~~~~~~~~~~ error: casting `i32` to `u8` may truncate the value - --> tests/ui/cast.rs:52:5 + --> tests/ui/cast.rs:53:5 | LL | 1i32 as u8; | ^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | u8::try_from(1i32); | ~~~~~~~~~~~~~~~~~~ error: casting `f64` to `isize` may truncate the value - --> tests/ui/cast.rs:54:5 + --> tests/ui/cast.rs:55:5 | LL | 1f64 as isize; | ^^^^^^^^^^^^^ @@ -105,7 +105,7 @@ LL | 1f64 as isize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may truncate the value - --> tests/ui/cast.rs:56:5 + --> tests/ui/cast.rs:57:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ @@ -113,13 +113,13 @@ LL | 1f64 as usize; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:56:5 + --> tests/ui/cast.rs:57:5 | LL | 1f64 as usize; | ^^^^^^^^^^^^^ error: casting `u32` to `u16` may truncate the value - --> tests/ui/cast.rs:59:5 + --> tests/ui/cast.rs:60:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^^^^^^^^ @@ -131,7 +131,7 @@ LL | u16::try_from(1f32 as u32); | ~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `f32` to `u32` may truncate the value - --> tests/ui/cast.rs:59:5 + --> tests/ui/cast.rs:60:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ @@ -139,13 +139,13 @@ LL | 1f32 as u32 as u16; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:59:5 + --> tests/ui/cast.rs:60:5 | LL | 1f32 as u32 as u16; | ^^^^^^^^^^^ error: casting `i32` to `i8` may truncate the value - --> tests/ui/cast.rs:64:22 + --> tests/ui/cast.rs:65:22 | LL | let _x: i8 = 1i32 as _; | ^^^^^^^^^ @@ -157,7 +157,7 @@ LL | let _x: i8 = 1i32.try_into(); | ~~~~~~~~~~~~~~~ error: casting `f32` to `i32` may truncate the value - --> tests/ui/cast.rs:66:9 + --> tests/ui/cast.rs:67:9 | LL | 1f32 as i32; | ^^^^^^^^^^^ @@ -165,7 +165,7 @@ LL | 1f32 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f64` to `i32` may truncate the value - --> tests/ui/cast.rs:68:9 + --> tests/ui/cast.rs:69:9 | LL | 1f64 as i32; | ^^^^^^^^^^^ @@ -173,7 +173,7 @@ LL | 1f64 as i32; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may truncate the value - --> tests/ui/cast.rs:70:9 + --> tests/ui/cast.rs:71:9 | LL | 1f32 as u8; | ^^^^^^^^^^ @@ -181,13 +181,13 @@ LL | 1f32 as u8; = help: if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ... error: casting `f32` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:70:9 + --> tests/ui/cast.rs:71:9 | LL | 1f32 as u8; | ^^^^^^^^^^ error: casting `u8` to `i8` may wrap around the value - --> tests/ui/cast.rs:75:5 + --> tests/ui/cast.rs:76:5 | LL | 1u8 as i8; | ^^^^^^^^^ @@ -196,31 +196,31 @@ LL | 1u8 as i8; = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` error: casting `u16` to `i16` may wrap around the value - --> tests/ui/cast.rs:78:5 + --> tests/ui/cast.rs:79:5 | LL | 1u16 as i16; | ^^^^^^^^^^^ error: casting `u32` to `i32` may wrap around the value - --> tests/ui/cast.rs:80:5 + --> tests/ui/cast.rs:81:5 | LL | 1u32 as i32; | ^^^^^^^^^^^ error: casting `u64` to `i64` may wrap around the value - --> tests/ui/cast.rs:82:5 + --> tests/ui/cast.rs:83:5 | LL | 1u64 as i64; | ^^^^^^^^^^^ error: casting `usize` to `isize` may wrap around the value - --> tests/ui/cast.rs:84:5 + --> tests/ui/cast.rs:85:5 | LL | 1usize as isize; | ^^^^^^^^^^^^^^^ error: casting `usize` to `i8` may truncate the value - --> tests/ui/cast.rs:87:5 + --> tests/ui/cast.rs:88:5 | LL | 1usize as i8; | ^^^^^^^^^^^^ @@ -232,7 +232,7 @@ LL | i8::try_from(1usize); | ~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i16` may truncate the value - --> tests/ui/cast.rs:90:5 + --> tests/ui/cast.rs:91:5 | LL | 1usize as i16; | ^^^^^^^^^^^^^ @@ -244,7 +244,7 @@ LL | i16::try_from(1usize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i16` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:90:5 + --> tests/ui/cast.rs:91:5 | LL | 1usize as i16; | ^^^^^^^^^^^^^ @@ -253,7 +253,7 @@ LL | 1usize as i16; = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:95:5 + --> tests/ui/cast.rs:96:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ @@ -265,19 +265,19 @@ LL | i32::try_from(1usize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:95:5 + --> tests/ui/cast.rs:96:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ error: casting `usize` to `i64` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:99:5 + --> tests/ui/cast.rs:100:5 | LL | 1usize as i64; | ^^^^^^^^^^^^^ error: casting `u16` to `isize` may wrap around the value on targets with 16-bit wide pointers - --> tests/ui/cast.rs:104:5 + --> tests/ui/cast.rs:105:5 | LL | 1u16 as isize; | ^^^^^^^^^^^^^ @@ -286,13 +286,13 @@ LL | 1u16 as isize; = note: for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:108:5 + --> tests/ui/cast.rs:109:5 | LL | 1u32 as isize; | ^^^^^^^^^^^^^ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:111:5 + --> tests/ui/cast.rs:112:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ @@ -304,55 +304,55 @@ LL | isize::try_from(1u64); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast.rs:111:5 + --> tests/ui/cast.rs:112:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:116:5 + --> tests/ui/cast.rs:117:5 | LL | -1i32 as u32; | ^^^^^^^^^^^^ error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:119:5 + --> tests/ui/cast.rs:120:5 | LL | -1isize as usize; | ^^^^^^^^^^^^^^^^ error: casting `i8` to `u8` may lose the sign of the value - --> tests/ui/cast.rs:130:5 + --> tests/ui/cast.rs:131:5 | LL | (i8::MIN).abs() as u8; | ^^^^^^^^^^^^^^^^^^^^^ error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:134:5 + --> tests/ui/cast.rs:135:5 | LL | (-1i64).abs() as u64; | ^^^^^^^^^^^^^^^^^^^^ error: casting `isize` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:135:5 + --> tests/ui/cast.rs:136:5 | LL | (-1isize).abs() as usize; | ^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:142:5 + --> tests/ui/cast.rs:143:5 | LL | (unsafe { (-1i64).checked_abs().unwrap_unchecked() }) as u64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i64` to `u64` may lose the sign of the value - --> tests/ui/cast.rs:157:5 + --> tests/ui/cast.rs:158:5 | LL | (unsafe { (-1i64).checked_isqrt().unwrap_unchecked() }) as u64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i64` to `i8` may truncate the value - --> tests/ui/cast.rs:208:5 + --> tests/ui/cast.rs:209:5 | LL | (-99999999999i64).min(1) as i8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -364,7 +364,7 @@ LL | i8::try_from((-99999999999i64).min(1)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:222:5 + --> tests/ui/cast.rs:223:5 | LL | 999999u64.clamp(0, 256) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -376,7 +376,7 @@ LL | u8::try_from(999999u64.clamp(0, 256)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E2` to `u8` may truncate the value - --> tests/ui/cast.rs:245:21 + --> tests/ui/cast.rs:246:21 | LL | let _ = self as u8; | ^^^^^^^^^^ @@ -388,7 +388,7 @@ LL | let _ = u8::try_from(self); | ~~~~~~~~~~~~~~~~~~ error: casting `main::E2::B` to `u8` will truncate the value - --> tests/ui/cast.rs:247:21 + --> tests/ui/cast.rs:248:21 | LL | let _ = Self::B as u8; | ^^^^^^^^^^^^^ @@ -397,7 +397,7 @@ LL | let _ = Self::B as u8; = help: to override `-D warnings` add `#[allow(clippy::cast_enum_truncation)]` error: casting `main::E5` to `i8` may truncate the value - --> tests/ui/cast.rs:289:21 + --> tests/ui/cast.rs:290:21 | LL | let _ = self as i8; | ^^^^^^^^^^ @@ -409,13 +409,13 @@ LL | let _ = i8::try_from(self); | ~~~~~~~~~~~~~~~~~~ error: casting `main::E5::A` to `i8` will truncate the value - --> tests/ui/cast.rs:291:21 + --> tests/ui/cast.rs:292:21 | LL | let _ = Self::A as i8; | ^^^^^^^^^^^^^ error: casting `main::E6` to `i16` may truncate the value - --> tests/ui/cast.rs:308:21 + --> tests/ui/cast.rs:309:21 | LL | let _ = self as i16; | ^^^^^^^^^^^ @@ -427,7 +427,7 @@ LL | let _ = i16::try_from(self); | ~~~~~~~~~~~~~~~~~~~ error: casting `main::E7` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:327:21 + --> tests/ui/cast.rs:328:21 | LL | let _ = self as usize; | ^^^^^^^^^^^^^ @@ -439,7 +439,7 @@ LL | let _ = usize::try_from(self); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `main::E10` to `u16` may truncate the value - --> tests/ui/cast.rs:374:21 + --> tests/ui/cast.rs:375:21 | LL | let _ = self as u16; | ^^^^^^^^^^^ @@ -451,7 +451,7 @@ LL | let _ = u16::try_from(self); | ~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:385:13 + --> tests/ui/cast.rs:386:13 | LL | let c = (q >> 16) as u8; | ^^^^^^^^^^^^^^^ @@ -463,7 +463,7 @@ LL | let c = u8::try_from(q >> 16); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:389:13 + --> tests/ui/cast.rs:390:13 | LL | let c = (q / 1000) as u8; | ^^^^^^^^^^^^^^^^ @@ -475,85 +475,85 @@ LL | let c = u8::try_from(q / 1000); | ~~~~~~~~~~~~~~~~~~~~~~ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:401:9 + --> tests/ui/cast.rs:402:9 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:406:32 + --> tests/ui/cast.rs:407:32 | LL | let _a = |x: i32| -> u32 { (x * x * x * x) as u32 }; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:408:5 + --> tests/ui/cast.rs:409:5 | LL | (2_i32).checked_pow(3).unwrap() as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:409:5 + --> tests/ui/cast.rs:410:5 | LL | (-2_i32).pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:414:5 + --> tests/ui/cast.rs:415:5 | LL | (-5_i32 % 2) as u32; | ^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:416:5 + --> tests/ui/cast.rs:417:5 | LL | (-5_i32 % -2) as u32; | ^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:419:5 + --> tests/ui/cast.rs:420:5 | LL | (-2_i32 >> 1) as u32; | ^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:423:5 + --> tests/ui/cast.rs:424:5 | LL | (x * x) as u32; | ^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:424:5 + --> tests/ui/cast.rs:425:5 | LL | (x * x * x) as u32; | ^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:428:5 + --> tests/ui/cast.rs:429:5 | LL | (y * y * y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:430:5 + --> tests/ui/cast.rs:431:5 | LL | (y * y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:431:5 + --> tests/ui/cast.rs:432:5 | LL | (y * y / y * 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:433:5 + --> tests/ui/cast.rs:434:5 | LL | (y / y * y * -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ error: equal expressions as operands to `/` - --> tests/ui/cast.rs:433:6 + --> tests/ui/cast.rs:434:6 | LL | (y / y * y * -2) as u16; | ^^^^^ @@ -561,97 +561,97 @@ LL | (y / y * y * -2) as u16; = note: `#[deny(clippy::eq_op)]` on by default error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:436:5 + --> tests/ui/cast.rs:437:5 | LL | (y + y + y + -2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:438:5 + --> tests/ui/cast.rs:439:5 | LL | (y + y + y + 2) as u16; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:442:5 + --> tests/ui/cast.rs:443:5 | LL | (z + -2) as u16; | ^^^^^^^^^^^^^^^ error: casting `i16` to `u16` may lose the sign of the value - --> tests/ui/cast.rs:444:5 + --> tests/ui/cast.rs:445:5 | LL | (z + z + 2) as u16; | ^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:447:9 + --> tests/ui/cast.rs:448:9 | LL | (a * a * b * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:448:9 + --> tests/ui/cast.rs:449:9 | LL | (a * b * c) as u32; | ^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:450:9 + --> tests/ui/cast.rs:451:9 | LL | (a * -b * c) as u32; | ^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:452:9 + --> tests/ui/cast.rs:453:9 | LL | (a * b * c * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:453:9 + --> tests/ui/cast.rs:454:9 | LL | (a * -2) as u32; | ^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:455:9 + --> tests/ui/cast.rs:456:9 | LL | (a * b * c * -2) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:457:9 + --> tests/ui/cast.rs:458:9 | LL | (a / b) as u32; | ^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:458:9 + --> tests/ui/cast.rs:459:9 | LL | (a / b * c) as u32; | ^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:460:9 + --> tests/ui/cast.rs:461:9 | LL | (a / b + b * c) as u32; | ^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:462:9 + --> tests/ui/cast.rs:463:9 | LL | a.saturating_pow(3) as u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:464:9 + --> tests/ui/cast.rs:465:9 | LL | (a.abs() * b.pow(2) / c.abs()) as u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `i32` to `u32` may lose the sign of the value - --> tests/ui/cast.rs:472:21 + --> tests/ui/cast.rs:473:21 | LL | let _ = i32::MIN as u32; // cast_sign_loss | ^^^^^^^^^^^^^^^ @@ -662,7 +662,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `u32` to `u8` may truncate the value - --> tests/ui/cast.rs:473:21 + --> tests/ui/cast.rs:474:21 | LL | let _ = u32::MAX as u8; // cast_possible_truncation | ^^^^^^^^^^^^^^ @@ -678,7 +678,7 @@ LL | let _ = u8::try_from(u32::MAX); // cast_possible_truncation | ~~~~~~~~~~~~~~~~~~~~~~ error: casting `f64` to `f32` may truncate the value - --> tests/ui/cast.rs:474:21 + --> tests/ui/cast.rs:475:21 | LL | let _ = std::f64::consts::PI as f32; // cast_possible_truncation | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -690,7 +690,7 @@ LL | m!(); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast.rs:483:5 + --> tests/ui/cast.rs:484:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -702,13 +702,13 @@ LL | usize::try_from(bar.unwrap().unwrap()) | error: casting `i64` to `usize` may lose the sign of the value - --> tests/ui/cast.rs:483:5 + --> tests/ui/cast.rs:484:5 | LL | bar.unwrap().unwrap() as usize | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:498:5 + --> tests/ui/cast.rs:499:5 | LL | (256 & 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -720,7 +720,7 @@ LL | u8::try_from(256 & 999999u64); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `u8` may truncate the value - --> tests/ui/cast.rs:500:5 + --> tests/ui/cast.rs:501:5 | LL | (255 % 999999u64) as u8; | ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/cast_size.32bit.stderr b/tests/ui/cast_size.32bit.stderr index eb6c59deabba2..8ff3464676c3f 100644 --- a/tests/ui/cast_size.32bit.stderr +++ b/tests/ui/cast_size.32bit.stderr @@ -12,35 +12,35 @@ help: ... or use `try_from` and handle the error accordingly LL | i8::try_from(1isize); | ~~~~~~~~~~~~~~~~~~~~ -error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:18:5 +error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> tests/ui/cast_size.rs:21:5 | -LL | x0 as f64; +LL | x0 as f32; | ^^^^^^^^^ | = note: `-D clippy::cast-precision-loss` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::cast_precision_loss)]` -error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:19:5 +error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) + --> tests/ui/cast_size.rs:22:5 | -LL | x1 as f64; +LL | x1 as f32; | ^^^^^^^^^ -error: casting `isize` to `f32` causes a loss of precision (`isize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:20:5 +error: casting `isize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`isize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> tests/ui/cast_size.rs:23:5 | -LL | x0 as f32; +LL | x0 as f64; | ^^^^^^^^^ -error: casting `usize` to `f32` causes a loss of precision (`usize` is 32 or 64 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:21:5 +error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) + --> tests/ui/cast_size.rs:24:5 | -LL | x1 as f32; +LL | x1 as f64; | ^^^^^^^^^ error: casting `isize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:22:5 + --> tests/ui/cast_size.rs:28:5 | LL | 1isize as i32; | ^^^^^^^^^^^^^ @@ -52,7 +52,7 @@ LL | i32::try_from(1isize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `isize` to `u32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:23:5 + --> tests/ui/cast_size.rs:29:5 | LL | 1isize as u32; | ^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | u32::try_from(1isize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `u32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:24:5 + --> tests/ui/cast_size.rs:30:5 | LL | 1usize as u32; | ^^^^^^^^^^^^^ @@ -76,7 +76,7 @@ LL | u32::try_from(1usize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i32` may truncate the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:25:5 + --> tests/ui/cast_size.rs:31:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ @@ -88,7 +88,7 @@ LL | i32::try_from(1usize); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `usize` to `i32` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:25:5 + --> tests/ui/cast_size.rs:31:5 | LL | 1usize as i32; | ^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ LL | 1usize as i32; = help: to override `-D warnings` add `#[allow(clippy::cast_possible_wrap)]` error: casting `i64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:26:5 + --> tests/ui/cast_size.rs:32:5 | LL | 1i64 as isize; | ^^^^^^^^^^^^^ @@ -109,7 +109,7 @@ LL | isize::try_from(1i64); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `i64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:27:5 + --> tests/ui/cast_size.rs:33:5 | LL | 1i64 as usize; | ^^^^^^^^^^^^^ @@ -121,7 +121,7 @@ LL | usize::try_from(1i64); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `isize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:28:5 + --> tests/ui/cast_size.rs:34:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ @@ -133,13 +133,13 @@ LL | isize::try_from(1u64); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u64` to `isize` may wrap around the value on targets with 64-bit wide pointers - --> tests/ui/cast_size.rs:28:5 + --> tests/ui/cast_size.rs:34:5 | LL | 1u64 as isize; | ^^^^^^^^^^^^^ error: casting `u64` to `usize` may truncate the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:29:5 + --> tests/ui/cast_size.rs:35:5 | LL | 1u64 as usize; | ^^^^^^^^^^^^^ @@ -151,25 +151,25 @@ LL | usize::try_from(1u64); | ~~~~~~~~~~~~~~~~~~~~~ error: casting `u32` to `isize` may wrap around the value on targets with 32-bit wide pointers - --> tests/ui/cast_size.rs:30:5 + --> tests/ui/cast_size.rs:36:5 | LL | 1u32 as isize; | ^^^^^^^^^^^^^ error: casting `i32` to `f32` causes a loss of precision (`i32` is 32 bits wide, but `f32`'s mantissa is only 23 bits wide) - --> tests/ui/cast_size.rs:35:5 + --> tests/ui/cast_size.rs:43:5 | LL | 999_999_999 as f32; | ^^^^^^^^^^^^^^^^^^ error: casting `usize` to `f64` causes a loss of precision on targets with 64-bit wide pointers (`usize` is 64 bits wide, but `f64`'s mantissa is only 52 bits wide) - --> tests/ui/cast_size.rs:36:5 + --> tests/ui/cast_size.rs:44:5 | LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: literal out of range for `usize` - --> tests/ui/cast_size.rs:36:5 + --> tests/ui/cast_size.rs:44:5 | LL | 9_999_999_999_999_999usize as f64; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/copy_iterator.rs b/tests/ui/copy_iterator.rs index c0e5fc3e44674..ea3a4face93fe 100644 --- a/tests/ui/copy_iterator.rs +++ b/tests/ui/copy_iterator.rs @@ -1,4 +1,5 @@ #![warn(clippy::copy_iterator)] +#![allow(clippy::manual_inspect)] #[derive(Copy, Clone)] struct Countdown(u8); diff --git a/tests/ui/copy_iterator.stderr b/tests/ui/copy_iterator.stderr index 533bddaadb5ec..990b1ce628dec 100644 --- a/tests/ui/copy_iterator.stderr +++ b/tests/ui/copy_iterator.stderr @@ -1,5 +1,5 @@ error: you are implementing `Iterator` on a `Copy` type - --> tests/ui/copy_iterator.rs:6:1 + --> tests/ui/copy_iterator.rs:7:1 | LL | / impl Iterator for Countdown { LL | | diff --git a/tests/ui/crashes/ice-12284.rs b/tests/ui/crashes/ice-12284.rs new file mode 100644 index 0000000000000..8d1dbface8eba --- /dev/null +++ b/tests/ui/crashes/ice-12284.rs @@ -0,0 +1,10 @@ +#![allow(incomplete_features)] +#![feature(unnamed_fields)] + +#[repr(C)] +struct Foo { + _: struct { + }, +} + +fn main() {} diff --git a/tests/ui/field_scoped_visibility_modifiers.rs b/tests/ui/field_scoped_visibility_modifiers.rs new file mode 100644 index 0000000000000..5789dbf9b1d72 --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.rs @@ -0,0 +1,21 @@ +#![warn(clippy::field_scoped_visibility_modifiers)] + +pub mod pub_module { + pub(in crate::pub_module) mod pub_in_path_module {} + pub(super) mod pub_super_module {} + struct MyStruct { + private_field: bool, + pub pub_field: bool, + pub(crate) pub_crate_field: bool, + pub(in crate::pub_module) pub_in_path_field: bool, + pub(super) pub_super_field: bool, + #[allow(clippy::needless_pub_self)] + pub(self) pub_self_field: bool, + } +} +pub(crate) mod pub_crate_module {} + +#[allow(clippy::needless_pub_self)] +pub(self) mod pub_self_module {} + +fn main() {} diff --git a/tests/ui/field_scoped_visibility_modifiers.stderr b/tests/ui/field_scoped_visibility_modifiers.stderr new file mode 100644 index 0000000000000..beea6c92107c7 --- /dev/null +++ b/tests/ui/field_scoped_visibility_modifiers.stderr @@ -0,0 +1,28 @@ +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:9:9 + | +LL | pub(crate) pub_crate_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + = note: `-D clippy::field-scoped-visibility-modifiers` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::field_scoped_visibility_modifiers)]` + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:10:9 + | +LL | pub(in crate::pub_module) pub_in_path_field: bool, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + +error: scoped visibility modifier on a field + --> tests/ui/field_scoped_visibility_modifiers.rs:11:9 + | +LL | pub(super) pub_super_field: bool, + | ^^^^^^^^^^ + | + = help: consider making the field private and adding a scoped visibility method for it + +error: aborting due to 3 previous errors + diff --git a/tests/ui/implicit_return.fixed b/tests/ui/implicit_return.fixed index ba73e64f18626..56fe29b4e677c 100644 --- a/tests/ui/implicit_return.fixed +++ b/tests/ui/implicit_return.fixed @@ -1,6 +1,11 @@ +//@aux-build: proc_macros.rs + #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] +extern crate proc_macros; +use proc_macros::with_span; + fn test_end_of_fn() -> bool { if true { // no error! @@ -136,3 +141,11 @@ fn check_expect() -> bool { #[expect(clippy::implicit_return)] true } + +with_span!( + span + + fn dont_lint_proc_macro(x: usize) -> usize{ + x + } +); diff --git a/tests/ui/implicit_return.rs b/tests/ui/implicit_return.rs index 522fc6a0a447c..f066ce20cfd1c 100644 --- a/tests/ui/implicit_return.rs +++ b/tests/ui/implicit_return.rs @@ -1,6 +1,11 @@ +//@aux-build: proc_macros.rs + #![warn(clippy::implicit_return)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] +extern crate proc_macros; +use proc_macros::with_span; + fn test_end_of_fn() -> bool { if true { // no error! @@ -136,3 +141,11 @@ fn check_expect() -> bool { #[expect(clippy::implicit_return)] true } + +with_span!( + span + + fn dont_lint_proc_macro(x: usize) -> usize{ + x + } +); diff --git a/tests/ui/implicit_return.stderr b/tests/ui/implicit_return.stderr index b2f7bc6943951..f06d4e983c57a 100644 --- a/tests/ui/implicit_return.stderr +++ b/tests/ui/implicit_return.stderr @@ -1,5 +1,5 @@ error: missing `return` statement - --> tests/ui/implicit_return.rs:10:5 + --> tests/ui/implicit_return.rs:15:5 | LL | true | ^^^^ help: add `return` as shown: `return true` @@ -8,85 +8,85 @@ LL | true = help: to override `-D warnings` add `#[allow(clippy::implicit_return)]` error: missing `return` statement - --> tests/ui/implicit_return.rs:14:15 + --> tests/ui/implicit_return.rs:19:15 | LL | if true { true } else { false } | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:14:29 + --> tests/ui/implicit_return.rs:19:29 | LL | if true { true } else { false } | ^^^^^ help: add `return` as shown: `return false` error: missing `return` statement - --> tests/ui/implicit_return.rs:20:17 + --> tests/ui/implicit_return.rs:25:17 | LL | true => false, | ^^^^^ help: add `return` as shown: `return false` error: missing `return` statement - --> tests/ui/implicit_return.rs:21:20 + --> tests/ui/implicit_return.rs:26:20 | LL | false => { true }, | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:34:9 + --> tests/ui/implicit_return.rs:39:9 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:41:13 + --> tests/ui/implicit_return.rs:46:13 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:49:13 + --> tests/ui/implicit_return.rs:54:13 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:67:18 + --> tests/ui/implicit_return.rs:72:18 | LL | let _ = || { true }; | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:68:16 + --> tests/ui/implicit_return.rs:73:16 | LL | let _ = || true; | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:76:5 + --> tests/ui/implicit_return.rs:81:5 | LL | format!("test {}", "test") | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")` error: missing `return` statement - --> tests/ui/implicit_return.rs:85:5 + --> tests/ui/implicit_return.rs:90:5 | LL | m!(true, false) | ^^^^^^^^^^^^^^^ help: add `return` as shown: `return m!(true, false)` error: missing `return` statement - --> tests/ui/implicit_return.rs:91:13 + --> tests/ui/implicit_return.rs:96:13 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> tests/ui/implicit_return.rs:96:17 + --> tests/ui/implicit_return.rs:101:17 | LL | break 'outer false; | ^^^^^^^^^^^^^^^^^^ help: change `break` to `return` as shown: `return false` error: missing `return` statement - --> tests/ui/implicit_return.rs:111:5 + --> tests/ui/implicit_return.rs:116:5 | LL | / loop { LL | | m!(true); @@ -101,7 +101,7 @@ LL + } | error: missing `return` statement - --> tests/ui/implicit_return.rs:125:5 + --> tests/ui/implicit_return.rs:130:5 | LL | true | ^^^^ help: add `return` as shown: `return true` diff --git a/tests/ui/manual_inspect.fixed b/tests/ui/manual_inspect.fixed new file mode 100644 index 0000000000000..0e1b8fe3edb54 --- /dev/null +++ b/tests/ui/manual_inspect.fixed @@ -0,0 +1,174 @@ +#![warn(clippy::manual_inspect)] +#![allow(clippy::no_effect, clippy::op_ref)] + +fn main() { + let _ = Some(0).inspect(|&x| { + println!("{}", x); + }); + + let _ = Some(0).inspect(|&x| { + println!("{x}"); + }); + + let _ = Some(0).inspect(|&x| { + println!("{}", x * 5 + 1); + }); + + let _ = Some(0).inspect(|&x| { + if x == 0 { + panic!(); + } + }); + + let _ = Some(0).inspect(|&x| { + if &x == &0 { + let _y = x; + panic!(); + } + }); + + let _ = Some(0).map(|x| { + let y = x + 1; + if y > 5 { + return y; + } + x + }); + + { + #[derive(PartialEq)] + struct Foo(i32); + + let _ = Some(Foo(0)).map(|x| { + if x == Foo(0) { + panic!(); + } + x + }); + + let _ = Some(Foo(0)).map(|x| { + if &x == &Foo(0) { + let _y = x; + panic!(); + } + x + }); + } + + { + macro_rules! maybe_ret { + ($e:expr) => { + if $e == 0 { + return $e; + } + }; + } + + let _ = Some(0).map(|x| { + maybe_ret!(x); + x + }); + } + + let _ = Some((String::new(), 0u32)).inspect(|x| { + if x.1 == 0 { + let _x = x.1; + panic!(); + } + }); + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.0; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + let _ = Some(String::new()).inspect(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + return; + } + println!("test"); + }); + + let _ = Some(0).inspect(|&x| { + if x == 0 { + let _ = || { + let _x = x; + }; + panic!(); + } + }); + + { + use core::cell::Cell; + #[derive(Debug)] + struct Cell2(core::cell::Cell); + + let _ = Some(Cell2(Cell::new(0u32))).inspect(|x| { + x.0.set(1); + }); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + let y = &x; + if x.0.get() == 0 { + y.0.set(1) + } else { + println!("{x:?}"); + } + x + }); + } + + let _: Result<_, ()> = Ok(0).inspect(|&x| { + println!("{}", x); + }); + + let _: Result<(), _> = Err(0).inspect_err(|&x| { + println!("{}", x); + }); + + let _ = [0] + .into_iter() + .inspect(|&x| { + println!("{}", x); + }) + .count(); + + { + struct S(T); + impl S { + fn map(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + + fn map_err(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + } + + let _ = S(0).map(|x| { + println!("{}", x); + x + }); + + let _ = S(0).map_err(|x| { + println!("{}", x); + x + }); + } +} diff --git a/tests/ui/manual_inspect.rs b/tests/ui/manual_inspect.rs new file mode 100644 index 0000000000000..94cdfe3914006 --- /dev/null +++ b/tests/ui/manual_inspect.rs @@ -0,0 +1,186 @@ +#![warn(clippy::manual_inspect)] +#![allow(clippy::no_effect, clippy::op_ref)] + +fn main() { + let _ = Some(0).map(|x| { + println!("{}", x); + x + }); + + let _ = Some(0).map(|x| { + println!("{x}"); + x + }); + + let _ = Some(0).map(|x| { + println!("{}", x * 5 + 1); + x + }); + + let _ = Some(0).map(|x| { + if x == 0 { + panic!(); + } + x + }); + + let _ = Some(0).map(|x| { + if &x == &0 { + let _y = x; + panic!(); + } + x + }); + + let _ = Some(0).map(|x| { + let y = x + 1; + if y > 5 { + return y; + } + x + }); + + { + #[derive(PartialEq)] + struct Foo(i32); + + let _ = Some(Foo(0)).map(|x| { + if x == Foo(0) { + panic!(); + } + x + }); + + let _ = Some(Foo(0)).map(|x| { + if &x == &Foo(0) { + let _y = x; + panic!(); + } + x + }); + } + + { + macro_rules! maybe_ret { + ($e:expr) => { + if $e == 0 { + return $e; + } + }; + } + + let _ = Some(0).map(|x| { + maybe_ret!(x); + x + }); + } + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.1; + panic!(); + } + x + }); + + let _ = Some((String::new(), 0u32)).map(|x| { + if x.1 == 0 { + let _x = x.0; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + let _ = Some(String::new()).map(|x| { + if x.is_empty() { + let _ = || { + let _x = &x; + }; + return x; + } + println!("test"); + x + }); + + let _ = Some(0).map(|x| { + if x == 0 { + let _ = || { + let _x = x; + }; + panic!(); + } + x + }); + + { + use core::cell::Cell; + #[derive(Debug)] + struct Cell2(core::cell::Cell); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + x.0.set(1); + x + }); + + let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + let y = &x; + if x.0.get() == 0 { + y.0.set(1) + } else { + println!("{x:?}"); + } + x + }); + } + + let _: Result<_, ()> = Ok(0).map(|x| { + println!("{}", x); + x + }); + + let _: Result<(), _> = Err(0).map_err(|x| { + println!("{}", x); + x + }); + + let _ = [0] + .into_iter() + .map(|x| { + println!("{}", x); + x + }) + .count(); + + { + struct S(T); + impl S { + fn map(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + + fn map_err(self, f: impl FnOnce(T) -> U) -> S { + S(f(self.0)) + } + } + + let _ = S(0).map(|x| { + println!("{}", x); + x + }); + + let _ = S(0).map_err(|x| { + println!("{}", x); + x + }); + } +} diff --git a/tests/ui/manual_inspect.stderr b/tests/ui/manual_inspect.stderr new file mode 100644 index 0000000000000..8548c0cd29424 --- /dev/null +++ b/tests/ui/manual_inspect.stderr @@ -0,0 +1,182 @@ +error: + --> tests/ui/manual_inspect.rs:5:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | + = note: `-D clippy::manual-inspect` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_inspect)]` +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{}", x); + | + +error: + --> tests/ui/manual_inspect.rs:10:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{x}"); + | + +error: + --> tests/ui/manual_inspect.rs:15:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL ~ println!("{}", x * 5 + 1); + | + +error: + --> tests/ui/manual_inspect.rs:20:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if x == 0 { +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:27:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if &x == &0 { +LL | let _y = x; +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:78:41 + | +LL | let _ = Some((String::new(), 0u32)).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some((String::new(), 0u32)).inspect(|x| { +LL | if x.1 == 0 { +LL | let _x = x.1; +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:104:33 + | +LL | let _ = Some(String::new()).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(String::new()).inspect(|x| { +LL | if x.is_empty() { +LL | let _ = || { +LL ~ let _x = x; +LL | }; +LL ~ return; +LL | } +LL ~ println!("test"); + | + +error: + --> tests/ui/manual_inspect.rs:115:21 + | +LL | let _ = Some(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(0).inspect(|&x| { +LL | if x == 0 { +... +LL | panic!(); +LL ~ } + | + +error: + --> tests/ui/manual_inspect.rs:130:46 + | +LL | let _ = Some(Cell2(Cell::new(0u32))).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _ = Some(Cell2(Cell::new(0u32))).inspect(|x| { +LL ~ x.0.set(1); + | + +error: + --> tests/ui/manual_inspect.rs:146:34 + | +LL | let _: Result<_, ()> = Ok(0).map(|x| { + | ^^^ + | +help: try + | +LL ~ let _: Result<_, ()> = Ok(0).inspect(|&x| { +LL ~ println!("{}", x); + | + +error: + --> tests/ui/manual_inspect.rs:151:35 + | +LL | let _: Result<(), _> = Err(0).map_err(|x| { + | ^^^^^^^ + | +help: try + | +LL ~ let _: Result<(), _> = Err(0).inspect_err(|&x| { +LL ~ println!("{}", x); + | + +error: this call to `map()` won't have an effect on the call to `count()` + --> tests/ui/manual_inspect.rs:156:13 + | +LL | let _ = [0] + | _____________^ +LL | | .into_iter() +LL | | .map(|x| { +LL | | println!("{}", x); +LL | | x +LL | | }) +LL | | .count(); + | |________________^ + | + = help: make sure you did not confuse `map` with `filter`, `for_each` or `inspect` + = note: `-D clippy::suspicious-map` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` + +error: + --> tests/ui/manual_inspect.rs:158:10 + | +LL | .map(|x| { + | ^^^ + | +help: try + | +LL ~ .inspect(|&x| { +LL ~ println!("{}", x); + | + +error: aborting due to 13 previous errors + diff --git a/tests/ui/manual_pattern_char_comparison.fixed b/tests/ui/manual_pattern_char_comparison.fixed index 588226b87e87f..03e621d95ba16 100644 --- a/tests/ui/manual_pattern_char_comparison.fixed +++ b/tests/ui/manual_pattern_char_comparison.fixed @@ -47,3 +47,15 @@ fn main() { } "".find(|c| m!(c)); } + +#[clippy::msrv = "1.57"] +fn msrv_1_57() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} + +#[clippy::msrv = "1.58"] +fn msrv_1_58() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(['.', ',', '!', '?']); +} diff --git a/tests/ui/manual_pattern_char_comparison.rs b/tests/ui/manual_pattern_char_comparison.rs index 5078f3ee27f36..43e883cd325a2 100644 --- a/tests/ui/manual_pattern_char_comparison.rs +++ b/tests/ui/manual_pattern_char_comparison.rs @@ -47,3 +47,15 @@ fn main() { } "".find(|c| m!(c)); } + +#[clippy::msrv = "1.57"] +fn msrv_1_57() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} + +#[clippy::msrv = "1.58"] +fn msrv_1_58() { + let sentence = "Hello, world!"; + sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); +} diff --git a/tests/ui/manual_pattern_char_comparison.stderr b/tests/ui/manual_pattern_char_comparison.stderr index b6b51794a11f5..f185d7c8f6767 100644 --- a/tests/ui/manual_pattern_char_comparison.stderr +++ b/tests/ui/manual_pattern_char_comparison.stderr @@ -55,5 +55,11 @@ error: this manual char comparison can be written more succinctly LL | sentence.find(|c| c == '🎈'); | ^^^^^^^^^^^^^ help: consider using a `char`: `'🎈'` -error: aborting due to 9 previous errors +error: this manual char comparison can be written more succinctly + --> tests/ui/manual_pattern_char_comparison.rs:60:31 + | +LL | sentence.trim_end_matches(|c: char| c == '.' || c == ',' || c == '!' || c == '?'); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using an array of `char`: `['.', ',', '!', '?']` + +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_unwrap_or.fixed b/tests/ui/manual_unwrap_or.fixed index dffd44b6a7c1b..74afa00e12f27 100644 --- a/tests/ui/manual_unwrap_or.fixed +++ b/tests/ui/manual_unwrap_or.fixed @@ -68,6 +68,32 @@ fn option_unwrap_or() { Some(s) => s, None => &format!("{} {}!", "hello", "world"), }; + + Some(1).unwrap_or(42); + + //don't lint + if let Some(x) = Some(1) { + x + 1 + } else { + 42 + }; + if let Some(x) = Some(1) { + x + } else { + return; + }; + for j in 0..4 { + if let Some(x) = Some(j) { + x + } else { + continue; + }; + if let Some(x) = Some(j) { + x + } else { + break; + }; + } } fn result_unwrap_or() { @@ -138,6 +164,32 @@ fn result_unwrap_or() { Ok(s) => s, Err(s) => "Bob", }; + + Ok::(1).unwrap_or(42); + + //don't lint + if let Ok(x) = Ok::(1) { + x + 1 + } else { + 42 + }; + if let Ok(x) = Ok::(1) { + x + } else { + return; + }; + for j in 0..4 { + if let Ok(x) = Ok::(j) { + x + } else { + continue; + }; + if let Ok(x) = Ok::(j) { + x + } else { + break; + }; + } } // don't lint in const fn diff --git a/tests/ui/manual_unwrap_or.rs b/tests/ui/manual_unwrap_or.rs index 67427132c1a85..2d01b8ceaaa75 100644 --- a/tests/ui/manual_unwrap_or.rs +++ b/tests/ui/manual_unwrap_or.rs @@ -83,6 +83,36 @@ fn option_unwrap_or() { Some(s) => s, None => &format!("{} {}!", "hello", "world"), }; + + if let Some(x) = Some(1) { + x + } else { + 42 + }; + + //don't lint + if let Some(x) = Some(1) { + x + 1 + } else { + 42 + }; + if let Some(x) = Some(1) { + x + } else { + return; + }; + for j in 0..4 { + if let Some(x) = Some(j) { + x + } else { + continue; + }; + if let Some(x) = Some(j) { + x + } else { + break; + }; + } } fn result_unwrap_or() { @@ -177,6 +207,36 @@ fn result_unwrap_or() { Ok(s) => s, Err(s) => "Bob", }; + + if let Ok(x) = Ok::(1) { + x + } else { + 42 + }; + + //don't lint + if let Ok(x) = Ok::(1) { + x + 1 + } else { + 42 + }; + if let Ok(x) = Ok::(1) { + x + } else { + return; + }; + for j in 0..4 { + if let Ok(x) = Ok::(j) { + x + } else { + continue; + }; + if let Ok(x) = Ok::(j) { + x + } else { + break; + }; + } } // don't lint in const fn diff --git a/tests/ui/manual_unwrap_or.stderr b/tests/ui/manual_unwrap_or.stderr index 33a099680ce0e..c93a8952a0805 100644 --- a/tests/ui/manual_unwrap_or.stderr +++ b/tests/ui/manual_unwrap_or.stderr @@ -58,8 +58,18 @@ LL | | None => "Alice", LL | | }; | |_____^ help: replace with: `Some("Bob").unwrap_or("Alice")` +error: this pattern reimplements `Option::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:87:5 + | +LL | / if let Some(x) = Some(1) { +LL | | x +LL | | } else { +LL | | 42 +LL | | }; + | |_____^ help: replace with: `Some(1).unwrap_or(42)` + error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:90:5 + --> tests/ui/manual_unwrap_or.rs:120:5 | LL | / match Ok::(1) { LL | | Ok(i) => i, @@ -68,7 +78,7 @@ LL | | }; | |_____^ help: replace with: `Ok::(1).unwrap_or(42)` error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:97:5 + --> tests/ui/manual_unwrap_or.rs:127:5 | LL | / match a { LL | | Ok(i) => i, @@ -77,7 +87,7 @@ LL | | }; | |_____^ help: replace with: `a.unwrap_or(42)` error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:103:5 + --> tests/ui/manual_unwrap_or.rs:133:5 | LL | / match Ok(1) as Result { LL | | Ok(i) => i, @@ -86,7 +96,7 @@ LL | | }; | |_____^ help: replace with: `(Ok(1) as Result).unwrap_or(42)` error: this pattern reimplements `Option::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:116:5 + --> tests/ui/manual_unwrap_or.rs:146:5 | LL | / match s.method() { LL | | Some(i) => i, @@ -95,7 +105,7 @@ LL | | }; | |_____^ help: replace with: `s.method().unwrap_or(42)` error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:122:5 + --> tests/ui/manual_unwrap_or.rs:152:5 | LL | / match Ok::(1) { LL | | Err(_) => 42, @@ -104,7 +114,7 @@ LL | | }; | |_____^ help: replace with: `Ok::(1).unwrap_or(42)` error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:128:5 + --> tests/ui/manual_unwrap_or.rs:158:5 | LL | / match Ok::(1) { LL | | Ok(i) => i, @@ -113,7 +123,7 @@ LL | | }; | |_____^ help: replace with: `Ok::(1).unwrap_or(1 + 42)` error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:135:5 + --> tests/ui/manual_unwrap_or.rs:165:5 | LL | / match Ok::(1) { LL | | Ok(i) => i, @@ -134,7 +144,7 @@ LL ~ }); | error: this pattern reimplements `Result::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:145:5 + --> tests/ui/manual_unwrap_or.rs:175:5 | LL | / match Ok::<&str, &str>("Bob") { LL | | Ok(i) => i, @@ -142,8 +152,18 @@ LL | | Err(_) => "Alice", LL | | }; | |_____^ help: replace with: `Ok::<&str, &str>("Bob").unwrap_or("Alice")` +error: this pattern reimplements `Result::unwrap_or` + --> tests/ui/manual_unwrap_or.rs:211:5 + | +LL | / if let Ok(x) = Ok::(1) { +LL | | x +LL | | } else { +LL | | 42 +LL | | }; + | |_____^ help: replace with: `Ok::(1).unwrap_or(42)` + error: this pattern reimplements `Option::unwrap_or` - --> tests/ui/manual_unwrap_or.rs:205:17 + --> tests/ui/manual_unwrap_or.rs:265:17 | LL | let _ = match some_macro!() { | _________________^ @@ -152,5 +172,5 @@ LL | | None => 0, LL | | }; | |_________^ help: replace with: `some_macro!().unwrap_or(0)` -error: aborting due to 14 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/manual_unwrap_or_default.fixed b/tests/ui/manual_unwrap_or_default.fixed index 663de1a5f0677..832376fa5af15 100644 --- a/tests/ui/manual_unwrap_or_default.fixed +++ b/tests/ui/manual_unwrap_or_default.fixed @@ -1,5 +1,5 @@ #![warn(clippy::manual_unwrap_or_default)] -#![allow(clippy::unnecessary_literal_unwrap)] +#![allow(clippy::unnecessary_literal_unwrap, clippy::manual_unwrap_or)] fn main() { let x: Option> = None; @@ -78,3 +78,24 @@ fn issue_12569() { 0 }; } + +// Should not warn! +fn issue_12928() { + let x = Some((1, 2)); + let y = if let Some((a, _)) = x { a } else { 0 }; + let y = if let Some((a, ..)) = x { a } else { 0 }; + let x = Some([1, 2]); + let y = if let Some([a, _]) = x { a } else { 0 }; + let y = if let Some([a, ..]) = x { a } else { 0 }; + + struct X { + a: u8, + b: u8, + } + let x = Some(X { a: 0, b: 0 }); + let y = if let Some(X { a, .. }) = x { a } else { 0 }; + struct Y(u8, u8); + let x = Some(Y(0, 0)); + let y = if let Some(Y(a, _)) = x { a } else { 0 }; + let y = if let Some(Y(a, ..)) = x { a } else { 0 }; +} diff --git a/tests/ui/manual_unwrap_or_default.rs b/tests/ui/manual_unwrap_or_default.rs index 75ffe09be9d4d..649f65c89fb0a 100644 --- a/tests/ui/manual_unwrap_or_default.rs +++ b/tests/ui/manual_unwrap_or_default.rs @@ -1,5 +1,5 @@ #![warn(clippy::manual_unwrap_or_default)] -#![allow(clippy::unnecessary_literal_unwrap)] +#![allow(clippy::unnecessary_literal_unwrap, clippy::manual_unwrap_or)] fn main() { let x: Option> = None; @@ -111,3 +111,24 @@ fn issue_12569() { 0 }; } + +// Should not warn! +fn issue_12928() { + let x = Some((1, 2)); + let y = if let Some((a, _)) = x { a } else { 0 }; + let y = if let Some((a, ..)) = x { a } else { 0 }; + let x = Some([1, 2]); + let y = if let Some([a, _]) = x { a } else { 0 }; + let y = if let Some([a, ..]) = x { a } else { 0 }; + + struct X { + a: u8, + b: u8, + } + let x = Some(X { a: 0, b: 0 }); + let y = if let Some(X { a, .. }) = x { a } else { 0 }; + struct Y(u8, u8); + let x = Some(Y(0, 0)); + let y = if let Some(Y(a, _)) = x { a } else { 0 }; + let y = if let Some(Y(a, ..)) = x { a } else { 0 }; +} diff --git a/tests/ui/match_result_ok.fixed b/tests/ui/match_result_ok.fixed index 76b26f5e4386d..117e0bc68ccc0 100644 --- a/tests/ui/match_result_ok.fixed +++ b/tests/ui/match_result_ok.fixed @@ -1,6 +1,11 @@ #![warn(clippy::match_result_ok)] #![allow(dead_code)] -#![allow(clippy::boxed_local, clippy::uninlined_format_args, clippy::manual_unwrap_or_default)] +#![allow( + clippy::boxed_local, + clippy::uninlined_format_args, + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or +)] // Checking `if` cases diff --git a/tests/ui/match_result_ok.rs b/tests/ui/match_result_ok.rs index d6f2475ba79c8..f8a5269024da5 100644 --- a/tests/ui/match_result_ok.rs +++ b/tests/ui/match_result_ok.rs @@ -1,6 +1,11 @@ #![warn(clippy::match_result_ok)] #![allow(dead_code)] -#![allow(clippy::boxed_local, clippy::uninlined_format_args, clippy::manual_unwrap_or_default)] +#![allow( + clippy::boxed_local, + clippy::uninlined_format_args, + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or +)] // Checking `if` cases diff --git a/tests/ui/match_result_ok.stderr b/tests/ui/match_result_ok.stderr index 0d42d59dc0136..b5b91cbe55344 100644 --- a/tests/ui/match_result_ok.stderr +++ b/tests/ui/match_result_ok.stderr @@ -1,5 +1,5 @@ error: matching on `Some` with `ok()` is redundant - --> tests/ui/match_result_ok.rs:8:5 + --> tests/ui/match_result_ok.rs:13:5 | LL | if let Some(y) = x.parse().ok() { y } else { 0 } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -12,7 +12,7 @@ LL | if let Ok(y) = x.parse() { y } else { 0 } | ~~~~~~~~~~~~~~~~~~~~~~~~ error: matching on `Some` with `ok()` is redundant - --> tests/ui/match_result_ok.rs:18:9 + --> tests/ui/match_result_ok.rs:23:9 | LL | if let Some(y) = x . parse() . ok () { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,7 +23,7 @@ LL | if let Ok(y) = x . parse() { | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: matching on `Some` with `ok()` is redundant - --> tests/ui/match_result_ok.rs:44:5 + --> tests/ui/match_result_ok.rs:49:5 | LL | while let Some(a) = wat.next().ok() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed new file mode 100644 index 0000000000000..921dcf0b16261 --- /dev/null +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -0,0 +1,173 @@ +#![warn(clippy::missing_const_for_fn)] +#![allow(incomplete_features, clippy::let_and_return, clippy::missing_transmute_annotations)] +#![feature(const_mut_refs)] +#![feature(const_trait_impl)] + +use std::mem::transmute; + +struct Game { + guess: i32, +} + +impl Game { + // Could be const + pub const fn new() -> Self { + //~^ ERROR: this could be a `const fn` + //~| NOTE: `-D clippy::missing-const-for-fn` implied by `-D warnings` + Self { guess: 42 } + } + + const fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { + //~^ ERROR: this could be a `const fn` + b + } +} + +// Could be const +const fn one() -> i32 { + //~^ ERROR: this could be a `const fn` + 1 +} + +// Could also be const +const fn two() -> i32 { + //~^ ERROR: this could be a `const fn` + let abc = 2; + abc +} + +// Could be const (since Rust 1.39) +const fn string() -> String { + //~^ ERROR: this could be a `const fn` + String::new() +} + +// Could be const +const unsafe fn four() -> i32 { + //~^ ERROR: this could be a `const fn` + 4 +} + +// Could also be const +const fn generic(t: T) -> T { + //~^ ERROR: this could be a `const fn` + t +} + +fn sub(x: u32) -> usize { + unsafe { transmute(&x) } +} + +const fn generic_arr(t: [T; 1]) -> T { + //~^ ERROR: this could be a `const fn` + t[0] +} + +mod with_drop { + pub struct A; + pub struct B; + impl Drop for A { + fn drop(&mut self) {} + } + + impl B { + // This can be const, because `a` is passed by reference + pub const fn b(self, a: &A) -> B { + //~^ ERROR: this could be a `const fn` + B + } + } +} + +#[clippy::msrv = "1.47.0"] +mod const_fn_stabilized_before_msrv { + // This could be const because `u8::is_ascii_digit` is a stable const function in 1.47. + const fn const_fn_stabilized_before_msrv(byte: u8) { + //~^ ERROR: this could be a `const fn` + byte.is_ascii_digit(); + } +} + +#[clippy::msrv = "1.45"] +fn msrv_1_45() -> i32 { + 45 +} + +#[clippy::msrv = "1.46"] +const fn msrv_1_46() -> i32 { + //~^ ERROR: this could be a `const fn` + 46 +} + +// Should not be const +fn main() {} + +struct D; + +impl const Drop for D { + fn drop(&mut self) { + todo!(); + } +} + +// Lint this, since it can be dropped in const contexts +// FIXME(effects) +fn d(this: D) {} + +mod msrv { + struct Foo(*const u8, &'static u8); + + impl Foo { + #[clippy::msrv = "1.58"] + const fn deref_ptr_can_be_const(self) -> usize { + //~^ ERROR: this could be a `const fn` + unsafe { *self.0 as usize } + } + + const fn deref_copied_val(self) -> usize { + //~^ ERROR: this could be a `const fn` + *self.1 as usize + } + } + + union Bar { + val: u8, + } + + #[clippy::msrv = "1.56"] + const fn union_access_can_be_const() { + //~^ ERROR: this could be a `const fn` + let bar = Bar { val: 1 }; + let _ = unsafe { bar.val }; + } +} + +mod issue12677 { + pub struct Wrapper { + pub strings: Vec, + } + + impl Wrapper { + #[must_use] + pub const fn new(strings: Vec) -> Self { + Self { strings } + } + + #[must_use] + pub const fn empty() -> Self { + Self { strings: Vec::new() } + } + } + + pub struct Other { + pub text: String, + pub vec: Vec, + } + + impl Other { + pub const fn new(text: String) -> Self { + let vec = Vec::new(); + Self { text, vec } + } + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 1c61c3e871324..8999af761e314 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -10,6 +10,10 @@ LL | | } | = note: `-D clippy::missing-const-for-fn` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::missing_const_for_fn)]` +help: make the function `const` + | +LL | pub const fn new() -> Self { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:20:5 @@ -19,6 +23,11 @@ LL | | LL | | b LL | | } | |_____^ + | +help: make the function `const` + | +LL | const fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:27:1 @@ -28,6 +37,11 @@ LL | | LL | | 1 LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn one() -> i32 { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:33:1 @@ -38,6 +52,11 @@ LL | | let abc = 2; LL | | abc LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn two() -> i32 { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:40:1 @@ -47,6 +66,11 @@ LL | | LL | | String::new() LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn string() -> String { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:46:1 @@ -56,6 +80,11 @@ LL | | LL | | 4 LL | | } | |_^ + | +help: make the function `const` + | +LL | const unsafe fn four() -> i32 { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:52:1 @@ -65,6 +94,11 @@ LL | | LL | | t LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn generic(t: T) -> T { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:61:1 @@ -74,6 +108,11 @@ LL | | LL | | t[0] LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn generic_arr(t: [T; 1]) -> T { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:75:9 @@ -83,6 +122,11 @@ LL | | LL | | B LL | | } | |_________^ + | +help: make the function `const` + | +LL | pub const fn b(self, a: &A) -> B { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:85:5 @@ -92,6 +136,11 @@ LL | | LL | | byte.is_ascii_digit(); LL | | } | |_____^ + | +help: make the function `const` + | +LL | const fn const_fn_stabilized_before_msrv(byte: u8) { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:97:1 @@ -101,6 +150,11 @@ LL | | LL | | 46 LL | | } | |_^ + | +help: make the function `const` + | +LL | const fn msrv_1_46() -> i32 { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:122:9 @@ -110,6 +164,11 @@ LL | | LL | | unsafe { *self.0 as usize } LL | | } | |_________^ + | +help: make the function `const` + | +LL | const fn deref_ptr_can_be_const(self) -> usize { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:127:9 @@ -119,6 +178,11 @@ LL | | LL | | *self.1 as usize LL | | } | |_________^ + | +help: make the function `const` + | +LL | const fn deref_copied_val(self) -> usize { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:138:5 @@ -129,6 +193,11 @@ LL | | let bar = Bar { val: 1 }; LL | | let _ = unsafe { bar.val }; LL | | } | |_____^ + | +help: make the function `const` + | +LL | const fn union_access_can_be_const() { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 @@ -137,6 +206,11 @@ LL | / pub fn new(strings: Vec) -> Self { LL | | Self { strings } LL | | } | |_________^ + | +help: make the function `const` + | +LL | pub const fn new(strings: Vec) -> Self { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:157:9 @@ -145,6 +219,11 @@ LL | / pub fn empty() -> Self { LL | | Self { strings: Vec::new() } LL | | } | |_________^ + | +help: make the function `const` + | +LL | pub const fn empty() -> Self { + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:168:9 @@ -154,6 +233,11 @@ LL | | let vec = Vec::new(); LL | | Self { text, vec } LL | | } | |_________^ + | +help: make the function `const` + | +LL | pub const fn new(text: String) -> Self { + | +++++ error: aborting due to 17 previous errors diff --git a/tests/ui/octal_escapes.rs b/tests/ui/octal_escapes.rs index 3915dfdb8418b..f2664e2fa67ff 100644 --- a/tests/ui/octal_escapes.rs +++ b/tests/ui/octal_escapes.rs @@ -2,25 +2,20 @@ #![warn(clippy::octal_escapes)] fn main() { - let _bad1 = "\033[0m"; - //~^ ERROR: octal-looking escape in string literal - let _bad2 = b"\033[0m"; - //~^ ERROR: octal-looking escape in byte string literal - let _bad3 = "\\\033[0m"; - //~^ ERROR: octal-looking escape in string literal + let _bad1 = "\033[0m"; //~ octal_escapes + let _bad2 = b"\033[0m"; //~ octal_escapes + let _bad3 = "\\\033[0m"; //~ octal_escapes // maximum 3 digits (\012 is the escape) - let _bad4 = "\01234567"; - //~^ ERROR: octal-looking escape in string literal - let _bad5 = "\0\03"; - //~^ ERROR: octal-looking escape in string literal + let _bad4 = "\01234567"; //~ octal_escapes + let _bad5 = "\0\03"; //~ octal_escapes let _bad6 = "Text-\055\077-MoreText"; - //~^ ERROR: octal-looking escape in string literal + //~^ octal_escapes + //~| octal_escapes let _bad7 = "EvenMoreText-\01\02-ShortEscapes"; - //~^ ERROR: octal-looking escape in string literal - let _bad8 = "锈\01锈"; - //~^ ERROR: octal-looking escape in string literal - let _bad9 = "锈\011锈"; - //~^ ERROR: octal-looking escape in string literal + //~^ octal_escapes + //~| octal_escapes + let _bad8 = "锈\01锈"; //~ octal_escapes + let _bad9 = "锈\011锈"; //~ octal_escapes let _good1 = "\\033[0m"; let _good2 = "\0\\0"; diff --git a/tests/ui/octal_escapes.stderr b/tests/ui/octal_escapes.stderr index 7ed9ee3ae2f4f..9343ba64a30b3 100644 --- a/tests/ui/octal_escapes.stderr +++ b/tests/ui/octal_escapes.stderr @@ -1,148 +1,170 @@ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:5:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:5:18 | LL | let _bad1 = "\033[0m"; - | ^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null character + = help: octal escapes are not supported, `\0` is always null = note: `-D clippy::octal-escapes` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::octal_escapes)]` -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad1 = "\x1b[0m"; - | ~~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | LL | let _bad1 = "\x0033[0m"; - | ~~~~~~~~~~~ + | ~~~~~~ -error: octal-looking escape in byte string literal - --> tests/ui/octal_escapes.rs:7:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:6:19 | LL | let _bad2 = b"\033[0m"; - | ^^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null byte -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad2 = b"\x1b[0m"; - | ~~~~~~~~~~ -help: if the null byte is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | LL | let _bad2 = b"\x0033[0m"; - | ~~~~~~~~~~~~ + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:9:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:7:20 | LL | let _bad3 = "\\\033[0m"; - | ^^^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad3 = "\\\x1b[0m"; - | ~~~~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | LL | let _bad3 = "\\\x0033[0m"; - | ~~~~~~~~~~~~~ + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:12:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:9:18 | LL | let _bad4 = "\01234567"; - | ^^^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad4 = "\x0a34567"; - | ~~~~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | LL | let _bad4 = "\x001234567"; - | ~~~~~~~~~~~~~ + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:14:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:10:20 | LL | let _bad5 = "\0\03"; - | ^^^^^^^ + | ^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad5 = "\0\x03"; - | ~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | -LL | let _bad5 = "\0\x003"; - | ~~~~~~~~~ +LL | let _bad5 = "\0\x0003"; + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:16:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:11:23 | LL | let _bad6 = "Text-\055\077-MoreText"; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | -LL | let _bad6 = "Text-\x2d\x3f-MoreText"; - | ~~~~~~~~~~~~~~~~~~~~~~~~ -help: if the null character is intended, disambiguate using +LL | let _bad6 = "Text-\x2d\077-MoreText"; + | ~~~~ +help: if a null escape is intended, disambiguate using | -LL | let _bad6 = "Text-\x0055\x0077-MoreText"; - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LL | let _bad6 = "Text-\x0055\077-MoreText"; + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:18:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:11:27 + | +LL | let _bad6 = "Text-\055\077-MoreText"; + | ^^^^ + | +help: if an octal escape is intended, use a hex escape instead + | +LL | let _bad6 = "Text-\055\x3f-MoreText"; + | ~~~~ +help: if a null escape is intended, disambiguate using + | +LL | let _bad6 = "Text-\055\x0077-MoreText"; + | ~~~~~~ + +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:14:31 + | +LL | let _bad7 = "EvenMoreText-\01\02-ShortEscapes"; + | ^^^ + | +help: if an octal escape is intended, use a hex escape instead + | +LL | let _bad7 = "EvenMoreText-\x01\02-ShortEscapes"; + | ~~~~ +help: if a null escape is intended, disambiguate using + | +LL | let _bad7 = "EvenMoreText-\x0001\02-ShortEscapes"; + | ~~~~~~ + +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:14:34 | LL | let _bad7 = "EvenMoreText-\01\02-ShortEscapes"; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | -LL | let _bad7 = "EvenMoreText-\x01\x02-ShortEscapes"; - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -help: if the null character is intended, disambiguate using +LL | let _bad7 = "EvenMoreText-\01\x02-ShortEscapes"; + | ~~~~ +help: if a null escape is intended, disambiguate using | -LL | let _bad7 = "EvenMoreText-\x001\x002-ShortEscapes"; - | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LL | let _bad7 = "EvenMoreText-\01\x0002-ShortEscapes"; + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:20:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:17:19 | LL | let _bad8 = "锈\01锈"; - | ^^^^^^^^^ + | ^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad8 = "锈\x01锈"; - | ~~~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | -LL | let _bad8 = "锈\x001锈"; - | ~~~~~~~~~~~ +LL | let _bad8 = "锈\x0001锈"; + | ~~~~~~ -error: octal-looking escape in string literal - --> tests/ui/octal_escapes.rs:22:17 +error: octal-looking escape in a literal + --> tests/ui/octal_escapes.rs:18:19 | LL | let _bad9 = "锈\011锈"; - | ^^^^^^^^^^ + | ^^^^ | - = help: octal escapes are not supported, `\0` is always a null character -help: if an octal escape was intended, use the hexadecimal representation instead +help: if an octal escape is intended, use a hex escape instead | LL | let _bad9 = "锈\x09锈"; - | ~~~~~~~~~~ -help: if the null character is intended, disambiguate using + | ~~~~ +help: if a null escape is intended, disambiguate using | LL | let _bad9 = "锈\x0011锈"; - | ~~~~~~~~~~~~ + | ~~~~~~ -error: aborting due to 9 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index eeab801b7da81..0ac89bf0d8eaf 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -4,7 +4,8 @@ clippy::equatable_if_let, clippy::let_unit_value, clippy::redundant_locals, - clippy::manual_unwrap_or_default + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/tests/ui/option_if_let_else.rs b/tests/ui/option_if_let_else.rs index 3e5b96d7c3162..b4f1b2cd1f7ff 100644 --- a/tests/ui/option_if_let_else.rs +++ b/tests/ui/option_if_let_else.rs @@ -4,7 +4,8 @@ clippy::equatable_if_let, clippy::let_unit_value, clippy::redundant_locals, - clippy::manual_unwrap_or_default + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or )] fn bad1(string: Option<&str>) -> (bool, &str) { diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index f5359a0c34f99..37ef791edb008 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -1,5 +1,5 @@ error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:11:5 + --> tests/ui/option_if_let_else.rs:12:5 | LL | / if let Some(x) = string { LL | | (true, x) @@ -12,19 +12,19 @@ LL | | } = help: to override `-D warnings` add `#[allow(clippy::option_if_let_else)]` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:29:13 + --> tests/ui/option_if_let_else.rs:30:13 | LL | let _ = if let Some(s) = *string { s.len() } else { 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.map_or(0, |s| s.len())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:30:13 + --> tests/ui/option_if_let_else.rs:31:13 | LL | let _ = if let Some(s) = &num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:31:13 + --> tests/ui/option_if_let_else.rs:32:13 | LL | let _ = if let Some(s) = &mut num { | _____________^ @@ -44,13 +44,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:37:13 + --> tests/ui/option_if_let_else.rs:38:13 | LL | let _ = if let Some(ref s) = num { s } else { &0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.as_ref().map_or(&0, |s| s)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:38:13 + --> tests/ui/option_if_let_else.rs:39:13 | LL | let _ = if let Some(mut s) = num { | _____________^ @@ -70,7 +70,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:44:13 + --> tests/ui/option_if_let_else.rs:45:13 | LL | let _ = if let Some(ref mut s) = num { | _____________^ @@ -90,7 +90,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:53:5 + --> tests/ui/option_if_let_else.rs:54:5 | LL | / if let Some(x) = arg { LL | | let y = x * x; @@ -109,7 +109,7 @@ LL + }) | error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:66:13 + --> tests/ui/option_if_let_else.rs:67:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -121,7 +121,7 @@ LL | | }; | |_____^ help: try: `arg.map_or_else(side_effect, |x| x)` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:75:13 + --> tests/ui/option_if_let_else.rs:76:13 | LL | let _ = if let Some(x) = arg { | _____________^ @@ -144,7 +144,7 @@ LL ~ }, |x| x * x * x * x); | error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:108:13 + --> tests/ui/option_if_let_else.rs:109:13 | LL | / if let Some(idx) = s.find('.') { LL | | vec![s[..idx].to_string(), s[idx..].to_string()] @@ -154,7 +154,7 @@ LL | | } | |_____________^ help: try: `s.find('.').map_or_else(|| vec![s.to_string()], |idx| vec![s[..idx].to_string(), s[idx..].to_string()])` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:119:5 + --> tests/ui/option_if_let_else.rs:120:5 | LL | / if let Ok(binding) = variable { LL | | println!("Ok {binding}"); @@ -177,13 +177,13 @@ LL + }) | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:143:13 + --> tests/ui/option_if_let_else.rs:144:13 | LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:153:13 + --> tests/ui/option_if_let_else.rs:154:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -205,13 +205,13 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:181:13 + --> tests/ui/option_if_let_else.rs:182:13 | LL | let _ = if let Some(x) = Some(0) { s.len() + x } else { s.len() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(0).map_or(s.len(), |x| s.len() + x)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:185:13 + --> tests/ui/option_if_let_else.rs:186:13 | LL | let _ = if let Some(x) = Some(0) { | _____________^ @@ -231,7 +231,7 @@ LL ~ }); | error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:224:13 + --> tests/ui/option_if_let_else.rs:225:13 | LL | let _ = match s { | _____________^ @@ -241,7 +241,7 @@ LL | | }; | |_____^ help: try: `s.map_or(1, |string| string.len())` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:228:13 + --> tests/ui/option_if_let_else.rs:229:13 | LL | let _ = match Some(10) { | _____________^ @@ -251,7 +251,7 @@ LL | | }; | |_____^ help: try: `Some(10).map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:234:13 + --> tests/ui/option_if_let_else.rs:235:13 | LL | let _ = match res { | _____________^ @@ -261,7 +261,7 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:238:13 + --> tests/ui/option_if_let_else.rs:239:13 | LL | let _ = match res { | _____________^ @@ -271,13 +271,13 @@ LL | | }; | |_____^ help: try: `res.map_or(1, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:242:13 + --> tests/ui/option_if_let_else.rs:243:13 | LL | let _ = if let Ok(a) = res { a + 1 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `res.map_or(5, |a| a + 1)` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:259:17 + --> tests/ui/option_if_let_else.rs:260:17 | LL | let _ = match initial { | _________________^ @@ -287,7 +287,7 @@ LL | | }; | |_________^ help: try: `initial.as_ref().map_or(42, |value| do_something(value))` error: use Option::map_or instead of an if let/else - --> tests/ui/option_if_let_else.rs:266:17 + --> tests/ui/option_if_let_else.rs:267:17 | LL | let _ = match initial { | _________________^ @@ -297,7 +297,7 @@ LL | | }; | |_________^ help: try: `initial.as_mut().map_or(42, |value| do_something2(value))` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:289:24 + --> tests/ui/option_if_let_else.rs:290:24 | LL | let mut _hashmap = if let Some(hm) = &opt { | ________________________^ @@ -308,7 +308,7 @@ LL | | }; | |_____^ help: try: `opt.as_ref().map_or_else(HashMap::new, |hm| hm.clone())` error: use Option::map_or_else instead of an if let/else - --> tests/ui/option_if_let_else.rs:295:19 + --> tests/ui/option_if_let_else.rs:296:19 | LL | let mut _hm = if let Some(hm) = &opt { hm.clone() } else { new_map!() }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.as_ref().map_or_else(|| new_map!(), |hm| hm.clone())` diff --git a/tests/ui/unnecessary_min_or_max.fixed b/tests/ui/unnecessary_min_or_max.fixed new file mode 100644 index 0000000000000..392f6dd1fee43 --- /dev/null +++ b/tests/ui/unnecessary_min_or_max.fixed @@ -0,0 +1,67 @@ +//@aux-build:external_consts.rs + +#![allow(unused)] +#![warn(clippy::unnecessary_min_or_max)] +#![allow(clippy::identity_op)] + +extern crate external_consts; + +const X: i32 = 1; + +fn main() { + // Both are Literals + let _ = (-6_i32); + let _ = 9; + let _ = 6; + let _ = 9_u32; + let _ = 6; + let _ = 7_u8; + + let x: u32 = 42; + // unsigned with zero + let _ = 0; + let _ = x; + let _ = 0_u32; + let _ = x; + + let x: i32 = 42; + // signed MIN + let _ = i32::MIN; + let _ = x; + let _ = i32::MIN; + let _ = x; + + let _ = i32::MIN - 0; + let _ = x; + + let _ = i32::MIN - 0; + + // The below cases shouldn't be lint + let mut min = u32::MAX; + for _ in 0..1000 { + min = min.min(random_u32()); + } + + let _ = 2.min(external_consts::MAGIC_NUMBER); + let _ = 2.max(external_consts::MAGIC_NUMBER); + let _ = external_consts::MAGIC_NUMBER.min(2); + let _ = external_consts::MAGIC_NUMBER.max(2); + + let _ = X.min(external_consts::MAGIC_NUMBER); + let _ = X.max(external_consts::MAGIC_NUMBER); + let _ = external_consts::MAGIC_NUMBER.min(X); + let _ = external_consts::MAGIC_NUMBER.max(X); + + let _ = X.max(12); + let _ = X.min(12); + let _ = 12.min(X); + let _ = 12.max(X); + let _ = (X + 1).max(12); + let _ = (X + 1).min(12); + let _ = 12.min(X - 1); + let _ = 12.max(X - 1); +} +fn random_u32() -> u32 { + // random number generator + 0 +} diff --git a/tests/ui/unnecessary_min_or_max.rs b/tests/ui/unnecessary_min_or_max.rs new file mode 100644 index 0000000000000..b03755e6d23d1 --- /dev/null +++ b/tests/ui/unnecessary_min_or_max.rs @@ -0,0 +1,67 @@ +//@aux-build:external_consts.rs + +#![allow(unused)] +#![warn(clippy::unnecessary_min_or_max)] +#![allow(clippy::identity_op)] + +extern crate external_consts; + +const X: i32 = 1; + +fn main() { + // Both are Literals + let _ = (-6_i32).min(9); + let _ = (-6_i32).max(9); + let _ = 9_u32.min(6); + let _ = 9_u32.max(6); + let _ = 6.min(7_u8); + let _ = 6.max(7_u8); + + let x: u32 = 42; + // unsigned with zero + let _ = 0.min(x); + let _ = 0.max(x); + let _ = x.min(0_u32); + let _ = x.max(0_u32); + + let x: i32 = 42; + // signed MIN + let _ = i32::MIN.min(x); + let _ = i32::MIN.max(x); + let _ = x.min(i32::MIN); + let _ = x.max(i32::MIN); + + let _ = x.min(i32::MIN - 0); + let _ = x.max(i32::MIN); + + let _ = x.min(i32::MIN - 0); + + // The below cases shouldn't be lint + let mut min = u32::MAX; + for _ in 0..1000 { + min = min.min(random_u32()); + } + + let _ = 2.min(external_consts::MAGIC_NUMBER); + let _ = 2.max(external_consts::MAGIC_NUMBER); + let _ = external_consts::MAGIC_NUMBER.min(2); + let _ = external_consts::MAGIC_NUMBER.max(2); + + let _ = X.min(external_consts::MAGIC_NUMBER); + let _ = X.max(external_consts::MAGIC_NUMBER); + let _ = external_consts::MAGIC_NUMBER.min(X); + let _ = external_consts::MAGIC_NUMBER.max(X); + + let _ = X.max(12); + let _ = X.min(12); + let _ = 12.min(X); + let _ = 12.max(X); + let _ = (X + 1).max(12); + let _ = (X + 1).min(12); + let _ = 12.min(X - 1); + let _ = 12.max(X - 1); +} +fn random_u32() -> u32 { + // random number generator + 0 +} diff --git a/tests/ui/unnecessary_min_or_max.stderr b/tests/ui/unnecessary_min_or_max.stderr new file mode 100644 index 0000000000000..f5cd31fbaf24b --- /dev/null +++ b/tests/ui/unnecessary_min_or_max.stderr @@ -0,0 +1,107 @@ +error: `(-6_i32)` is never greater than `9` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:13:13 + | +LL | let _ = (-6_i32).min(9); + | ^^^^^^^^^^^^^^^ help: try: `(-6_i32)` + | + = note: `-D clippy::unnecessary-min-or-max` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_min_or_max)]` + +error: `(-6_i32)` is never greater than `9` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:14:13 + | +LL | let _ = (-6_i32).max(9); + | ^^^^^^^^^^^^^^^ help: try: `9` + +error: `9_u32` is never smaller than `6` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:15:13 + | +LL | let _ = 9_u32.min(6); + | ^^^^^^^^^^^^ help: try: `6` + +error: `9_u32` is never smaller than `6` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:16:13 + | +LL | let _ = 9_u32.max(6); + | ^^^^^^^^^^^^ help: try: `9_u32` + +error: `6` is never greater than `7_u8` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:17:13 + | +LL | let _ = 6.min(7_u8); + | ^^^^^^^^^^^ help: try: `6` + +error: `6` is never greater than `7_u8` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:18:13 + | +LL | let _ = 6.max(7_u8); + | ^^^^^^^^^^^ help: try: `7_u8` + +error: `0` is never greater than `x` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:22:13 + | +LL | let _ = 0.min(x); + | ^^^^^^^^ help: try: `0` + +error: `0` is never greater than `x` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:23:13 + | +LL | let _ = 0.max(x); + | ^^^^^^^^ help: try: `x` + +error: `x` is never smaller than `0_u32` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:24:13 + | +LL | let _ = x.min(0_u32); + | ^^^^^^^^^^^^ help: try: `0_u32` + +error: `x` is never smaller than `0_u32` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:25:13 + | +LL | let _ = x.max(0_u32); + | ^^^^^^^^^^^^ help: try: `x` + +error: `i32::MIN` is never greater than `x` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:29:13 + | +LL | let _ = i32::MIN.min(x); + | ^^^^^^^^^^^^^^^ help: try: `i32::MIN` + +error: `i32::MIN` is never greater than `x` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:30:13 + | +LL | let _ = i32::MIN.max(x); + | ^^^^^^^^^^^^^^^ help: try: `x` + +error: `x` is never smaller than `i32::MIN` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:31:13 + | +LL | let _ = x.min(i32::MIN); + | ^^^^^^^^^^^^^^^ help: try: `i32::MIN` + +error: `x` is never smaller than `i32::MIN` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:32:13 + | +LL | let _ = x.max(i32::MIN); + | ^^^^^^^^^^^^^^^ help: try: `x` + +error: `x` is never smaller than `i32::MIN - 0` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:34:13 + | +LL | let _ = x.min(i32::MIN - 0); + | ^^^^^^^^^^^^^^^^^^^ help: try: `i32::MIN - 0` + +error: `x` is never smaller than `i32::MIN` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:35:13 + | +LL | let _ = x.max(i32::MIN); + | ^^^^^^^^^^^^^^^ help: try: `x` + +error: `x` is never smaller than `i32::MIN - 0` and has therefore no effect + --> tests/ui/unnecessary_min_or_max.rs:37:13 + | +LL | let _ = x.min(i32::MIN - 0); + | ^^^^^^^^^^^^^^^^^^^ help: try: `i32::MIN - 0` + +error: aborting due to 17 previous errors + diff --git a/triagebot.toml b/triagebot.toml index 4d66b728b76df..0f0e62670ffe4 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -22,7 +22,6 @@ contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIB users_on_vacation = [ "matthiaskrgr", "giraffate", - "Centri3", ] [assign.owners] @@ -32,10 +31,10 @@ users_on_vacation = [ "*" = [ "@Manishearth", "@llogiq", - "@xFrednet", "@Alexendoo", "@dswij", "@Jarcho", "@blyxyas", "@y21", + "@Centri3", ] diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index c88c298d5d787..8de36fc40051c 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -103,6 +103,7 @@ @media (min-width: 405px) { #upper-filters { display: flex; + flex-wrap: wrap; } } @@ -404,7 +405,7 @@

Clippy Lints

-
+
- +
+ + +
-
+
Clippy Lints
-
+

diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7cca298df8e45..921bb0376f6c3 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -156,6 +156,18 @@ Object.entries(versionFilterKeyMap).map(([key, value]) => [value, key]) ); + const APPLICABILITIES_FILTER_DEFAULT = { + Unspecified: true, + Unresolved: true, + MachineApplicable: true, + MaybeIncorrect: true, + HasPlaceholders: true + }; + + $scope.applicabilities = { + ...APPLICABILITIES_FILTER_DEFAULT + } + // loadFromURLParameters retrieves filter settings from the URL parameters and assigns them // to corresponding $scope variables. function loadFromURLParameters() { @@ -182,6 +194,7 @@ handleParameter('levels', $scope.levels, LEVEL_FILTERS_DEFAULT); handleParameter('groups', $scope.groups, GROUPS_FILTER_DEFAULT); + handleParameter('applicabilities', $scope.applicabilities, APPLICABILITIES_FILTER_DEFAULT); // Handle 'versions' parameter separately because it needs additional processing if (urlParameters.versions) { @@ -249,6 +262,7 @@ updateURLParameter($scope.levels, 'levels', LEVEL_FILTERS_DEFAULT); updateURLParameter($scope.groups, 'groups', GROUPS_FILTER_DEFAULT); updateVersionURLParameter($scope.versionFilters); + updateURLParameter($scope.applicabilities, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT); } // Add $watches to automatically update URL parameters when the data changes @@ -270,6 +284,12 @@ } }, true); + $scope.$watch('applicabilities', function (newVal, oldVal) { + if (newVal !== oldVal) { + updateURLParameter(newVal, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT) + } + }, true); + // Watch for changes in the URL path and update the search and lint display $scope.$watch(function () { return $location.path(); }, function (newPath) { const searchParameter = newPath.substring(1); @@ -327,6 +347,15 @@ } }; + $scope.toggleApplicabilities = function (value) { + const applicabilities = $scope.applicabilities; + for (const key in applicabilities) { + if (applicabilities.hasOwnProperty(key)) { + applicabilities[key] = value; + } + } + } + $scope.resetGroupsToDefault = function () { $scope.groups = { ...GROUPS_FILTER_DEFAULT @@ -430,6 +459,10 @@ return true; } + $scope.byApplicabilities = function (lint) { + return $scope.applicabilities[lint.applicability.applicability]; + }; + // Show details for one lint $scope.openLint = function (lint) { $scope.open[lint.id] = true; From 39a215531cc975d4dd50486f9d71187bdfad1086 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 27 Jun 2024 14:56:57 -0400 Subject: [PATCH 014/107] Tighten spans for async blocks --- clippy_lints/src/suspicious_operation_groupings.rs | 2 +- clippy_utils/src/ast_utils.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index ab1b3043f0c38..1c1de805db0c6 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -549,7 +549,7 @@ fn ident_difference_expr_with_base_location( | (Assign(_, _, _), Assign(_, _, _)) | (TryBlock(_), TryBlock(_)) | (Await(_, _), Await(_, _)) - | (Gen(_, _, _), Gen(_, _, _)) + | (Gen(_, _, _, _), Gen(_, _, _, _)) | (Block(_, _), Block(_, _)) | (Closure(_), Closure(_)) | (Match(_, _, _), Match(_, _, _)) diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 785d5ed5dbeba..4b8d1c5ee9f38 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -226,7 +226,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { && eq_fn_decl(lf, rf) && eq_expr(le, re) }, - (Gen(lc, lb, lk), Gen(rc, rb, rk)) => lc == rc && eq_block(lb, rb) && lk == rk, + (Gen(lc, lb, lk, _), Gen(rc, rb, rk, _)) => lc == rc && eq_block(lb, rb) && lk == rk, (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), From 6de87829da801c10db378e28dd1797434383e651 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 27 Jun 2024 16:35:43 -0700 Subject: [PATCH 015/107] doc_lazy_continuation: blank comment line for gap This change addresses cases where doc comments are separated by blank lines, comments, or non-doc-comment attributes, like this: ```rust /// - first line // not part of doc comment /// second line ``` Before this commit, Clippy gave a pedantically-correct warning about how you needed to indent the second line. This is unlikely to be what the user intends, and has been described as a "false positive" (since Clippy is warning you about a highly unintuitive behavior that Rustdoc actually has, we definitely want it to output *something*, but the suggestion to indent was poor). https://github.com/rust-lang/rust-clippy/issues/12917 --- clippy_lints/src/doc/lazy_continuation.rs | 29 +++++++++++- clippy_lints/src/doc/mod.rs | 1 + tests/ui/doc/doc_lazy_blank_line.fixed | 47 +++++++++++++++++++ tests/ui/doc/doc_lazy_blank_line.rs | 43 +++++++++++++++++ tests/ui/doc/doc_lazy_blank_line.stderr | 56 +++++++++++++++++++++++ tests/ui/doc/doc_lazy_blockquote.fixed | 12 ++--- tests/ui/doc/doc_lazy_blockquote.rs | 12 ++--- tests/ui/doc/doc_lazy_blockquote.stderr | 12 ++--- tests/ui/doc/doc_lazy_list.fixed | 31 +++++++------ tests/ui/doc/doc_lazy_list.rs | 22 ++++----- tests/ui/doc/doc_lazy_list.stderr | 49 ++++++++++---------- 11 files changed, 246 insertions(+), 68 deletions(-) create mode 100644 tests/ui/doc/doc_lazy_blank_line.fixed create mode 100644 tests/ui/doc/doc_lazy_blank_line.rs create mode 100644 tests/ui/doc/doc_lazy_blank_line.stderr diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs index 38bc58a550196..bd1cc46e1850e 100644 --- a/clippy_lints/src/doc/lazy_continuation.rs +++ b/clippy_lints/src/doc/lazy_continuation.rs @@ -22,6 +22,7 @@ pub(super) fn check( range: Range, mut span: Span, containers: &[super::Container], + line_break_span: Span, ) { if doc[range.clone()].contains('\t') { // We don't do tab stops correctly. @@ -46,11 +47,35 @@ pub(super) fn check( .sum(); if ccount < blockquote_level || lcount < list_indentation { let msg = if ccount < blockquote_level { - "doc quote missing `>` marker" + "doc quote line without `>` marker" } else { - "doc list item missing indentation" + "doc list item without indentation" }; span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { + let snippet = clippy_utils::source::snippet(cx, line_break_span, ""); + if snippet.chars().filter(|&c| c == '\n').count() > 1 + && let Some(doc_comment_start) = snippet.rfind('\n') + && let doc_comment = snippet[doc_comment_start..].trim() + && (doc_comment == "///" || doc_comment == "//!") + { + // suggest filling in a blank line + diag.span_suggestion_with_style( + line_break_span.shrink_to_lo(), + "if this should be its own paragraph, add a blank doc comment line", + format!("\n{doc_comment}"), + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + if ccount > 0 || blockquote_level > 0 { + diag.help("if this not intended to be a quote at all, escape it with `\\>`"); + } else { + let indent = list_indentation - lcount; + diag.help(format!( + "if this is intended to be part of the list, indent {indent} spaces" + )); + } + return; + } if ccount == 0 && blockquote_level == 0 { // simpler suggestion style for indentation let indent = list_indentation - lcount; diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 3d875e7ac2d3f..5faa00b7c9718 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -762,6 +762,7 @@ fn check_doc<'a, Events: Iterator, Range blockquote code path +/// + +/// bottom text +pub const E: i32 = 20; + +/// > blockquote code path +/// +#[repr(C)] +/// bottom text +pub struct Foo(i32); diff --git a/tests/ui/doc/doc_lazy_blank_line.rs b/tests/ui/doc/doc_lazy_blank_line.rs new file mode 100644 index 0000000000000..e1ab8fc838926 --- /dev/null +++ b/tests/ui/doc/doc_lazy_blank_line.rs @@ -0,0 +1,43 @@ +// https://github.com/rust-lang/rust-clippy/issues/12917 +#![warn(clippy::doc_lazy_continuation)] + +/// This is a constant. +/// +/// The meaning of which should not be explained. +pub const A: i32 = 42; + +/// This is another constant, no longer used. +/// +/// This block of documentation has a long +/// explanation and derivation to explain +/// why it is what it is, and how it's used. +/// +/// It is left here for historical reasons, and +/// for reference. +/// +/// Reasons it's great: +/// - First reason +/// - Second reason +//pub const B: i32 = 1337; + +/// This is yet another constant. +/// +/// This has a similar fate as `B`. +/// +/// Reasons it's useful: +/// 1. First reason +/// 2. Second reason +//pub const C: i32 = 8008; + +/// This is still in use. +pub const D: i32 = 20; + +/// > blockquote code path + +/// bottom text +pub const E: i32 = 20; + +/// > blockquote code path +#[repr(C)] +/// bottom text +pub struct Foo(i32); diff --git a/tests/ui/doc/doc_lazy_blank_line.stderr b/tests/ui/doc/doc_lazy_blank_line.stderr new file mode 100644 index 0000000000000..854906a747418 --- /dev/null +++ b/tests/ui/doc/doc_lazy_blank_line.stderr @@ -0,0 +1,56 @@ +error: doc list item without indentation + --> tests/ui/doc/doc_lazy_blank_line.rs:23:5 + | +LL | /// This is yet another constant. + | ^ + | + = help: if this is intended to be part of the list, indent 3 spaces + = note: `-D clippy::doc-lazy-continuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]` +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// - Second reason +LL + /// + | + +error: doc list item without indentation + --> tests/ui/doc/doc_lazy_blank_line.rs:32:5 + | +LL | /// This is still in use. + | ^ + | + = help: if this is intended to be part of the list, indent 4 spaces +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// 2. Second reason +LL + /// + | + +error: doc quote line without `>` marker + --> tests/ui/doc/doc_lazy_blank_line.rs:37:5 + | +LL | /// bottom text + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// > blockquote code path +LL + /// + | + +error: doc quote line without `>` marker + --> tests/ui/doc/doc_lazy_blank_line.rs:42:5 + | +LL | /// bottom text + | ^ + | + = help: if this not intended to be a quote at all, escape it with `\>` +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// > blockquote code path +LL + /// + | + +error: aborting due to 4 previous errors + diff --git a/tests/ui/doc/doc_lazy_blockquote.fixed b/tests/ui/doc/doc_lazy_blockquote.fixed index 9877991f183a5..9d6e8637608fb 100644 --- a/tests/ui/doc/doc_lazy_blockquote.fixed +++ b/tests/ui/doc/doc_lazy_blockquote.fixed @@ -2,7 +2,7 @@ /// > blockquote with /// > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn first() {} /// > blockquote with no @@ -18,24 +18,24 @@ fn two_nowarn() {} /// > /// > > nest here /// > > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn two() {} /// > nest here /// > /// > > nest here /// > > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn three() {} /// > * > nest here /// > > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn four() {} /// > * > nest here /// > > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn four_point_1() {} /// * > nest here lazy continuation @@ -43,5 +43,5 @@ fn five() {} /// 1. > nest here /// > lazy continuation (this results in strange indentation, but still works) -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn six() {} diff --git a/tests/ui/doc/doc_lazy_blockquote.rs b/tests/ui/doc/doc_lazy_blockquote.rs index 587b2fdd533ce..0323a1b44e7cc 100644 --- a/tests/ui/doc/doc_lazy_blockquote.rs +++ b/tests/ui/doc/doc_lazy_blockquote.rs @@ -2,7 +2,7 @@ /// > blockquote with /// lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn first() {} /// > blockquote with no @@ -18,24 +18,24 @@ fn two_nowarn() {} /// > /// > > nest here /// > lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn two() {} /// > nest here /// > /// > > nest here /// lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn three() {} /// > * > nest here /// lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn four() {} /// > * > nest here /// lazy continuation -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn four_point_1() {} /// * > nest here lazy continuation @@ -43,5 +43,5 @@ fn five() {} /// 1. > nest here /// lazy continuation (this results in strange indentation, but still works) -//~^ ERROR: doc quote missing `>` marker +//~^ ERROR: doc quote line without `>` marker fn six() {} diff --git a/tests/ui/doc/doc_lazy_blockquote.stderr b/tests/ui/doc/doc_lazy_blockquote.stderr index 975184a01c3f0..d3390efdff375 100644 --- a/tests/ui/doc/doc_lazy_blockquote.stderr +++ b/tests/ui/doc/doc_lazy_blockquote.stderr @@ -1,4 +1,4 @@ -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:4:5 | LL | /// lazy continuation @@ -12,7 +12,7 @@ help: add markers to start of line LL | /// > lazy continuation | + -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:20:5 | LL | /// > lazy continuation @@ -24,7 +24,7 @@ help: add markers to start of line LL | /// > > lazy continuation | + -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:27:5 | LL | /// lazy continuation @@ -36,7 +36,7 @@ help: add markers to start of line LL | /// > > lazy continuation | +++ -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:32:5 | LL | /// lazy continuation @@ -48,7 +48,7 @@ help: add markers to start of line LL | /// > > lazy continuation | +++++++ -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:37:5 | LL | /// lazy continuation @@ -60,7 +60,7 @@ help: add markers to start of line LL | /// > > lazy continuation | +++++ -error: doc quote missing `>` marker +error: doc quote line without `>` marker --> tests/ui/doc/doc_lazy_blockquote.rs:45:5 | LL | /// lazy continuation (this results in strange indentation, but still works) diff --git a/tests/ui/doc/doc_lazy_list.fixed b/tests/ui/doc/doc_lazy_list.fixed index 409e6b0bc227f..ea59ae4c01c96 100644 --- a/tests/ui/doc/doc_lazy_list.fixed +++ b/tests/ui/doc/doc_lazy_list.fixed @@ -2,38 +2,41 @@ /// 1. nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn one() {} /// 1. first line /// lazy list continuations don't make warnings with this lint -//~^ ERROR: doc list item missing indentation -/// because they don't have the -//~^ ERROR: doc list item missing indentation +/// +//~^ ERROR: doc list item without indentation +/// because they don't have the +//~^ ERROR: doc list item without indentation fn two() {} /// - nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn three() {} /// - first line /// lazy list continuations don't make warnings with this lint -//~^ ERROR: doc list item missing indentation -/// because they don't have the -//~^ ERROR: doc list item missing indentation +/// +//~^ ERROR: doc list item without indentation +/// because they don't have the +//~^ ERROR: doc list item without indentation fn four() {} /// - nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn five() {} /// - - first line /// this will warn on the lazy continuation -//~^ ERROR: doc list item missing indentation -/// and so should this -//~^ ERROR: doc list item missing indentation +/// +//~^ ERROR: doc list item without indentation +/// and so should this +//~^ ERROR: doc list item without indentation fn six() {} /// - - first line @@ -54,7 +57,7 @@ fn seven() {} /// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors /// to set up. Example: /// 'protocol_descriptors': [ -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation /// { /// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP /// 'params': [ @@ -73,5 +76,5 @@ fn seven() {} /// }] /// } /// ] -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn eight() {} diff --git a/tests/ui/doc/doc_lazy_list.rs b/tests/ui/doc/doc_lazy_list.rs index 30ab448a1130c..3cc18e35780a2 100644 --- a/tests/ui/doc/doc_lazy_list.rs +++ b/tests/ui/doc/doc_lazy_list.rs @@ -2,38 +2,38 @@ /// 1. nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn one() {} /// 1. first line /// lazy list continuations don't make warnings with this lint -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation /// because they don't have the -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn two() {} /// - nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn three() {} /// - first line /// lazy list continuations don't make warnings with this lint -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation /// because they don't have the -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn four() {} /// - nest here /// lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn five() {} /// - - first line /// this will warn on the lazy continuation -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation /// and so should this -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn six() {} /// - - first line @@ -54,7 +54,7 @@ fn seven() {} /// * `protocol_descriptors`: A Json Representation of the ProtocolDescriptors /// to set up. Example: /// 'protocol_descriptors': [ -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation /// { /// 'protocol': 25, # u64 Representation of ProtocolIdentifier::AVDTP /// 'params': [ @@ -73,5 +73,5 @@ fn seven() {} /// }] /// } /// ] -//~^ ERROR: doc list item missing indentation +//~^ ERROR: doc list item without indentation fn eight() {} diff --git a/tests/ui/doc/doc_lazy_list.stderr b/tests/ui/doc/doc_lazy_list.stderr index ddfdc49340c45..52aa74df8948c 100644 --- a/tests/ui/doc/doc_lazy_list.stderr +++ b/tests/ui/doc/doc_lazy_list.stderr @@ -1,4 +1,4 @@ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:4:5 | LL | /// lazy continuation @@ -12,7 +12,7 @@ help: indent this line LL | /// lazy continuation | +++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:9:5 | LL | /// lazy list continuations don't make warnings with this lint @@ -24,19 +24,20 @@ help: indent this line LL | /// lazy list continuations don't make warnings with this lint | +++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:11:5 | LL | /// because they don't have the | ^ | - = help: if this is supposed to be its own paragraph, add a blank line -help: indent this line + = help: if this is intended to be part of the list, indent 3 spaces +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// lazy list continuations don't make warnings with this lint +LL + /// | -LL | /// because they don't have the - | +++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:16:5 | LL | /// lazy continuation @@ -48,7 +49,7 @@ help: indent this line LL | /// lazy continuation | ++++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:21:5 | LL | /// lazy list continuations don't make warnings with this lint @@ -60,19 +61,20 @@ help: indent this line LL | /// lazy list continuations don't make warnings with this lint | ++++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:23:5 | LL | /// because they don't have the | ^ | - = help: if this is supposed to be its own paragraph, add a blank line -help: indent this line + = help: if this is intended to be part of the list, indent 4 spaces +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// lazy list continuations don't make warnings with this lint +LL + /// | -LL | /// because they don't have the - | ++++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:28:5 | LL | /// lazy continuation @@ -84,7 +86,7 @@ help: indent this line LL | /// lazy continuation | ++++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:33:5 | LL | /// this will warn on the lazy continuation @@ -96,19 +98,20 @@ help: indent this line LL | /// this will warn on the lazy continuation | ++++++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:35:5 | LL | /// and so should this | ^^^^ | - = help: if this is supposed to be its own paragraph, add a blank line -help: indent this line + = help: if this is intended to be part of the list, indent 2 spaces +help: if this should be its own paragraph, add a blank doc comment line + | +LL ~ /// this will warn on the lazy continuation +LL + /// | -LL | /// and so should this - | ++ -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:56:5 | LL | /// 'protocol_descriptors': [ @@ -120,7 +123,7 @@ help: indent this line LL | /// 'protocol_descriptors': [ | + -error: doc list item missing indentation +error: doc list item without indentation --> tests/ui/doc/doc_lazy_list.rs:75:5 | LL | /// ] From 01a6dfa29fce5fe0375318b6504526cbd41d277e Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Fri, 28 Jun 2024 10:29:18 +0200 Subject: [PATCH 016/107] Add error message to manual_inspect lint --- clippy_lints/src/methods/manual_inspect.rs | 26 +++++++++++++--------- tests/ui/manual_inspect.stderr | 24 ++++++++++---------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/methods/manual_inspect.rs b/clippy_lints/src/methods/manual_inspect.rs index e3ce64c246a54..cac2e11f59166 100644 --- a/clippy_lints/src/methods/manual_inspect.rs +++ b/clippy_lints/src/methods/manual_inspect.rs @@ -167,14 +167,12 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: } else { edits.extend(addr_of_edits); } - edits.push(( - name_span, - String::from(match name { - "map" => "inspect", - "map_err" => "inspect_err", - _ => return, - }), - )); + let edit = match name { + "map" => "inspect", + "map_err" => "inspect_err", + _ => return, + }; + edits.push((name_span, edit.to_string())); edits.push(( final_expr .span @@ -187,9 +185,15 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: } else { Applicability::MachineApplicable }; - span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| { - diag.multipart_suggestion("try", edits, app); - }); + span_lint_and_then( + cx, + MANUAL_INSPECT, + name_span, + format!("using `{name}` over `{edit}`"), + |diag| { + diag.multipart_suggestion("try", edits, app); + }, + ); } } } diff --git a/tests/ui/manual_inspect.stderr b/tests/ui/manual_inspect.stderr index 8548c0cd29424..0559b3bd6611d 100644 --- a/tests/ui/manual_inspect.stderr +++ b/tests/ui/manual_inspect.stderr @@ -1,4 +1,4 @@ -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:5:21 | LL | let _ = Some(0).map(|x| { @@ -12,7 +12,7 @@ LL ~ let _ = Some(0).inspect(|&x| { LL ~ println!("{}", x); | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:10:21 | LL | let _ = Some(0).map(|x| { @@ -24,7 +24,7 @@ LL ~ let _ = Some(0).inspect(|&x| { LL ~ println!("{x}"); | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:15:21 | LL | let _ = Some(0).map(|x| { @@ -36,7 +36,7 @@ LL ~ let _ = Some(0).inspect(|&x| { LL ~ println!("{}", x * 5 + 1); | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:20:21 | LL | let _ = Some(0).map(|x| { @@ -50,7 +50,7 @@ LL | panic!(); LL ~ } | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:27:21 | LL | let _ = Some(0).map(|x| { @@ -65,7 +65,7 @@ LL | panic!(); LL ~ } | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:78:41 | LL | let _ = Some((String::new(), 0u32)).map(|x| { @@ -80,7 +80,7 @@ LL | panic!(); LL ~ } | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:104:33 | LL | let _ = Some(String::new()).map(|x| { @@ -98,7 +98,7 @@ LL | } LL ~ println!("test"); | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:115:21 | LL | let _ = Some(0).map(|x| { @@ -113,7 +113,7 @@ LL | panic!(); LL ~ } | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:130:46 | LL | let _ = Some(Cell2(Cell::new(0u32))).map(|x| { @@ -125,7 +125,7 @@ LL ~ let _ = Some(Cell2(Cell::new(0u32))).inspect(|x| { LL ~ x.0.set(1); | -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:146:34 | LL | let _: Result<_, ()> = Ok(0).map(|x| { @@ -137,7 +137,7 @@ LL ~ let _: Result<_, ()> = Ok(0).inspect(|&x| { LL ~ println!("{}", x); | -error: +error: using `map_err` over `inspect_err` --> tests/ui/manual_inspect.rs:151:35 | LL | let _: Result<(), _> = Err(0).map_err(|x| { @@ -166,7 +166,7 @@ LL | | .count(); = note: `-D clippy::suspicious-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` -error: +error: using `map` over `inspect` --> tests/ui/manual_inspect.rs:158:10 | LL | .map(|x| { From 74bc964e60eee642297face703940c6c8c198cf5 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Tue, 25 Jun 2024 10:44:00 +0000 Subject: [PATCH 017/107] finishing touches, move fixed ICEs to ui tests --- .../ui/missing_const_for_fn/could_be_const.rs | 3 +++ .../could_be_const.stderr | 20 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 58e639cc7fd1f..5e4e2c58e5a4d 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -104,15 +104,18 @@ fn main() {} struct D; +/* FIXME(effects) impl const Drop for D { fn drop(&mut self) { todo!(); } } +*/ // Lint this, since it can be dropped in const contexts // FIXME(effects) fn d(this: D) {} +//~^ ERROR: this could be a `const fn` mod msrv { struct Foo(*const u8, &'static u8); diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 8999af761e314..8ba42c0e5b673 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -157,7 +157,13 @@ LL | const fn msrv_1_46() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:122:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:117:1 + | +LL | fn d(this: D) {} + | ^^^^^^^^^^^^^^^^ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:125:9 | LL | / fn deref_ptr_can_be_const(self) -> usize { LL | | @@ -171,7 +177,7 @@ LL | const fn deref_ptr_can_be_const(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:127:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:130:9 | LL | / fn deref_copied_val(self) -> usize { LL | | @@ -185,7 +191,7 @@ LL | const fn deref_copied_val(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:138:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:141:5 | LL | / fn union_access_can_be_const() { LL | | @@ -200,7 +206,7 @@ LL | const fn union_access_can_be_const() { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:155:9 | LL | / pub fn new(strings: Vec) -> Self { LL | | Self { strings } @@ -213,7 +219,7 @@ LL | pub const fn new(strings: Vec) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:157:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:160:9 | LL | / pub fn empty() -> Self { LL | | Self { strings: Vec::new() } @@ -226,7 +232,7 @@ LL | pub const fn empty() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:168:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:171:9 | LL | / pub fn new(text: String) -> Self { LL | | let vec = Vec::new(); @@ -239,5 +245,5 @@ help: make the function `const` LL | pub const fn new(text: String) -> Self { | +++++ -error: aborting due to 17 previous errors +error: aborting due to 18 previous errors From 2fcef6e9f7bb3e94bf889fa3846afbfb25c991b8 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Wed, 26 Jun 2024 16:36:42 +0000 Subject: [PATCH 018/107] address review comments --- tests/ui/missing_const_for_fn/could_be_const.fixed | 5 ++++- tests/ui/missing_const_for_fn/could_be_const.stderr | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 921dcf0b16261..f8fc935f3679d 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -104,15 +104,18 @@ fn main() {} struct D; +/* FIXME(effects) impl const Drop for D { fn drop(&mut self) { todo!(); } } +*/ // Lint this, since it can be dropped in const contexts // FIXME(effects) -fn d(this: D) {} +const fn d(this: D) {} +//~^ ERROR: this could be a `const fn` mod msrv { struct Foo(*const u8, &'static u8); diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 8ba42c0e5b673..8302b074127e6 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -161,6 +161,11 @@ error: this could be a `const fn` | LL | fn d(this: D) {} | ^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const fn d(this: D) {} + | +++++ error: this could be a `const fn` --> tests/ui/missing_const_for_fn/could_be_const.rs:125:9 From eb33c1ac74d0e209b320bbc58c4a9c8085154797 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 28 Jun 2024 22:48:46 +0100 Subject: [PATCH 019/107] Image-related valid idents --- book/src/lint_configuration.md | 2 +- clippy_config/src/conf.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index cfd34c7d2a7d1..4fc109c7851f7 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -454,7 +454,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DevOps", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index dbab3b106a80f..9cc119c68cb12 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -31,6 +31,7 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "OCaml", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "WebGL", "WebGL2", "WebGPU", + "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", From b08b8b8a75a37db53bdc7756914a8a37c36a79c3 Mon Sep 17 00:00:00 2001 From: Roman Franchuk Date: Sun, 23 Jun 2024 12:30:20 +0200 Subject: [PATCH 020/107] Implement a lint to replace bit manual rotations with rotate_left/rotate_right --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_rotate.rs | 117 +++++++++++++++++++++++++++++ tests/ui/manual_rotate.fixed | 31 ++++++++ tests/ui/manual_rotate.rs | 31 ++++++++ tests/ui/manual_rotate.stderr | 71 +++++++++++++++++ 7 files changed, 254 insertions(+) create mode 100644 clippy_lints/src/manual_rotate.rs create mode 100644 tests/ui/manual_rotate.fixed create mode 100644 tests/ui/manual_rotate.rs create mode 100644 tests/ui/manual_rotate.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ef2c7936406..0974631ac5dc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5539,6 +5539,7 @@ Released 2018-09-13 [`manual_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns [`manual_rem_euclid`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rem_euclid [`manual_retain`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain +[`manual_rotate`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_rotate [`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic [`manual_slice_size_calculation`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_slice_size_calculation [`manual_split_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 638de5e818c8e..53f346c04e787 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -313,6 +313,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_retain::MANUAL_RETAIN_INFO, + crate::manual_rotate::MANUAL_ROTATE_INFO, crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO, crate::manual_string_new::MANUAL_STRING_NEW_INFO, crate::manual_strip::MANUAL_STRIP_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ed0ff51fbf65a..fa4c7084f4dd4 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -211,6 +211,7 @@ mod manual_non_exhaustive; mod manual_range_patterns; mod manual_rem_euclid; mod manual_retain; +mod manual_rotate; mod manual_slice_size_calculation; mod manual_string_new; mod manual_strip; @@ -1023,6 +1024,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(msrv()))); store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(msrv()))); + store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); store.register_late_pass(move |_| { Box::new(operators::Operators::new( verbose_bit_mask_threshold, diff --git a/clippy_lints/src/manual_rotate.rs b/clippy_lints/src/manual_rotate.rs new file mode 100644 index 0000000000000..a517a4d507526 --- /dev/null +++ b/clippy_lints/src/manual_rotate.rs @@ -0,0 +1,117 @@ +use std::fmt::Display; + +use clippy_utils::consts::{constant, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// It detects manual bit rotations that could be rewritten using standard + /// functions `rotate_left` or `rotate_right`. + /// + /// ### Why is this bad? + /// + /// Calling the function better conveys the intent. + /// + /// ### Known issues + /// + /// Currently, the lint only catches shifts by constant amount. + /// + /// ### Example + /// ```no_run + /// let x = 12345678_u32; + /// let _ = (x >> 8) | (x << 24); + /// ``` + /// Use instead: + /// ```no_run + /// let x = 12345678_u32; + /// let _ = x.rotate_right(8); + /// ``` + #[clippy::version = "1.81.0"] + pub MANUAL_ROTATE, + style, + "using bit shifts to rotate integers" +} + +declare_lint_pass!(ManualRotate => [MANUAL_ROTATE]); + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ShiftDirection { + Left, + Right, +} + +impl Display for ShiftDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Left => "rotate_left", + Self::Right => "rotate_right", + }) + } +} + +fn parse_shift<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option<(ShiftDirection, u128, &'tcx Expr<'tcx>)> { + if let ExprKind::Binary(op, l, r) = expr.kind { + let dir = match op.node { + BinOpKind::Shl => ShiftDirection::Left, + BinOpKind::Shr => ShiftDirection::Right, + _ => return None, + }; + let const_expr = constant(cx, cx.typeck_results(), r)?; + if let Constant::Int(shift) = const_expr { + return Some((dir, shift, l)); + } + } + None +} + +impl LateLintPass<'_> for ManualRotate { + fn check_expr<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Binary(op, l, r) = expr.kind + && let BinOpKind::Add | BinOpKind::BitOr = op.node + && let Some((l_shift_dir, l_amount, l_expr)) = parse_shift(cx, l) + && let Some((r_shift_dir, r_amount, r_expr)) = parse_shift(cx, r) + { + if l_shift_dir == r_shift_dir { + return; + } + if !clippy_utils::eq_expr_value(cx, l_expr, r_expr) { + return; + } + let Some(bit_width) = (match cx.typeck_results().expr_ty(expr).kind() { + ty::Int(itype) => itype.bit_width(), + ty::Uint(itype) => itype.bit_width(), + _ => return, + }) else { + return; + }; + if l_amount + r_amount == u128::from(bit_width) { + let (shift_function, amount) = if l_amount < r_amount { + (l_shift_dir, l_amount) + } else { + (r_shift_dir, r_amount) + }; + let mut applicability = Applicability::MachineApplicable; + let expr_sugg = sugg::Sugg::hir_with_applicability(cx, l_expr, "_", &mut applicability).maybe_par(); + span_lint_and_sugg( + cx, + MANUAL_ROTATE, + expr.span, + "there is no need to manually implement bit rotation", + "this expression can be rewritten as", + format!("{expr_sugg}.{shift_function}({amount})"), + Applicability::MachineApplicable, + ); + } + } + } +} diff --git a/tests/ui/manual_rotate.fixed b/tests/ui/manual_rotate.fixed new file mode 100644 index 0000000000000..5d33838a31877 --- /dev/null +++ b/tests/ui/manual_rotate.fixed @@ -0,0 +1,31 @@ +#![warn(clippy::manual_rotate)] +#![allow(unused)] +fn main() { + let (x_u8, x_u16, x_u32, x_u64) = (1u8, 1u16, 1u32, 1u64); + let (x_i8, x_i16, x_i32, x_i64) = (1i8, 1i16, 1i32, 1i64); + let a_u32 = 1u32; + // True positives + let y_u8 = x_u8.rotate_right(3); + let y_u16 = x_u16.rotate_right(7); + let y_u32 = x_u32.rotate_right(8); + let y_u64 = x_u64.rotate_right(9); + let y_i8 = x_i8.rotate_right(3); + let y_i16 = x_i16.rotate_right(7); + let y_i32 = x_i32.rotate_right(8); + let y_i64 = x_i64.rotate_right(9); + // Plus also works instead of | + let y_u32_plus = x_u32.rotate_right(8); + // Complex expression + let y_u32_complex = (x_u32 | 3256).rotate_right(8); + let y_u64_as = (x_u32 as u64).rotate_right(8); + + // False positives - can't be replaced with a rotation + let y_u8_false = (x_u8 >> 6) | (x_u8 << 3); + let y_u32_false = (x_u32 >> 8) | (x_u32 >> 24); + let y_u64_false2 = (x_u64 >> 9) & (x_u64 << 55); + // Variable mismatch + let y_u32_wrong_vars = (x_u32 >> 8) | (a_u32 << 24); + // Has side effects and therefore should not be matched + let mut l = vec![12_u8, 34]; + let y = (l.pop().unwrap() << 3) + (l.pop().unwrap() >> 5); +} diff --git a/tests/ui/manual_rotate.rs b/tests/ui/manual_rotate.rs new file mode 100644 index 0000000000000..5377491fb1a38 --- /dev/null +++ b/tests/ui/manual_rotate.rs @@ -0,0 +1,31 @@ +#![warn(clippy::manual_rotate)] +#![allow(unused)] +fn main() { + let (x_u8, x_u16, x_u32, x_u64) = (1u8, 1u16, 1u32, 1u64); + let (x_i8, x_i16, x_i32, x_i64) = (1i8, 1i16, 1i32, 1i64); + let a_u32 = 1u32; + // True positives + let y_u8 = (x_u8 >> 3) | (x_u8 << 5); + let y_u16 = (x_u16 >> 7) | (x_u16 << 9); + let y_u32 = (x_u32 >> 8) | (x_u32 << 24); + let y_u64 = (x_u64 >> 9) | (x_u64 << 55); + let y_i8 = (x_i8 >> 3) | (x_i8 << 5); + let y_i16 = (x_i16 >> 7) | (x_i16 << 9); + let y_i32 = (x_i32 >> 8) | (x_i32 << 24); + let y_i64 = (x_i64 >> 9) | (x_i64 << 55); + // Plus also works instead of | + let y_u32_plus = (x_u32 >> 8) + (x_u32 << 24); + // Complex expression + let y_u32_complex = ((x_u32 | 3256) >> 8) | ((x_u32 | 3256) << 24); + let y_u64_as = (x_u32 as u64 >> 8) | ((x_u32 as u64) << 56); + + // False positives - can't be replaced with a rotation + let y_u8_false = (x_u8 >> 6) | (x_u8 << 3); + let y_u32_false = (x_u32 >> 8) | (x_u32 >> 24); + let y_u64_false2 = (x_u64 >> 9) & (x_u64 << 55); + // Variable mismatch + let y_u32_wrong_vars = (x_u32 >> 8) | (a_u32 << 24); + // Has side effects and therefore should not be matched + let mut l = vec![12_u8, 34]; + let y = (l.pop().unwrap() << 3) + (l.pop().unwrap() >> 5); +} diff --git a/tests/ui/manual_rotate.stderr b/tests/ui/manual_rotate.stderr new file mode 100644 index 0000000000000..52da0861f7003 --- /dev/null +++ b/tests/ui/manual_rotate.stderr @@ -0,0 +1,71 @@ +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:8:16 + | +LL | let y_u8 = (x_u8 >> 3) | (x_u8 << 5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u8.rotate_right(3)` + | + = note: `-D clippy::manual-rotate` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_rotate)]` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:9:17 + | +LL | let y_u16 = (x_u16 >> 7) | (x_u16 << 9); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u16.rotate_right(7)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:10:17 + | +LL | let y_u32 = (x_u32 >> 8) | (x_u32 << 24); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u32.rotate_right(8)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:11:17 + | +LL | let y_u64 = (x_u64 >> 9) | (x_u64 << 55); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u64.rotate_right(9)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:12:16 + | +LL | let y_i8 = (x_i8 >> 3) | (x_i8 << 5); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i8.rotate_right(3)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:13:17 + | +LL | let y_i16 = (x_i16 >> 7) | (x_i16 << 9); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i16.rotate_right(7)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:14:17 + | +LL | let y_i32 = (x_i32 >> 8) | (x_i32 << 24); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i32.rotate_right(8)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:15:17 + | +LL | let y_i64 = (x_i64 >> 9) | (x_i64 << 55); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_i64.rotate_right(9)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:17:22 + | +LL | let y_u32_plus = (x_u32 >> 8) + (x_u32 << 24); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `x_u32.rotate_right(8)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:19:25 + | +LL | let y_u32_complex = ((x_u32 | 3256) >> 8) | ((x_u32 | 3256) << 24); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `(x_u32 | 3256).rotate_right(8)` + +error: there is no need to manually implement bit rotation + --> tests/ui/manual_rotate.rs:20:20 + | +LL | let y_u64_as = (x_u32 as u64 >> 8) | ((x_u32 as u64) << 56); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: this expression can be rewritten as: `(x_u32 as u64).rotate_right(8)` + +error: aborting due to 11 previous errors + From eb1b24a97a5794fe62e83964700ec233b8dd5f32 Mon Sep 17 00:00:00 2001 From: Miguel Ojeda Date: Sun, 30 Jun 2024 23:56:39 +0200 Subject: [PATCH 021/107] `manual_inspect`: fix `clippy::version` from 1.78.0 to 1.81.0 Although `manual_inspect`'s PR started some months ago, the lint is only available in the current nightly (1.81.0), rather than 1.78.0. Signed-off-by: Miguel Ojeda --- clippy_lints/src/methods/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 1408f45482007..c5d44e620f285 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -4121,7 +4121,7 @@ declare_clippy_lint! { /// ```no_run /// let x = Some(0).inspect(|x| println!("{x}")); /// ``` - #[clippy::version = "1.78.0"] + #[clippy::version = "1.81.0"] pub MANUAL_INSPECT, complexity, "use of `map` returning the original item" From 0dddf6e0e8af27d363a6e69e67cfbe1d1ae294d8 Mon Sep 17 00:00:00 2001 From: asemia Date: Mon, 1 Jul 2024 14:09:15 +0500 Subject: [PATCH 022/107] Fix doc for verbose_bit_mask --- clippy_lints/src/operators/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 48a442705b294..202ee61506c07 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -242,6 +242,13 @@ declare_clippy_lint! { /// # let x = 1; /// if (x | 1 > 3) { } /// ``` + /// + /// Use instead: + /// + /// ```no_run + /// # let x = 1; + /// if (x >= 2) { } + /// ``` #[clippy::version = "pre 1.29.0"] pub INEFFECTIVE_BIT_MASK, correctness, @@ -265,6 +272,13 @@ declare_clippy_lint! { /// # let x = 1; /// if x & 0b1111 == 0 { } /// ``` + /// + /// Use instead: + /// + /// ```no_run + /// # let x: i32 = 1; + /// if x.trailing_zeros() > 4 { } + /// ``` #[clippy::version = "pre 1.29.0"] pub VERBOSE_BIT_MASK, pedantic, From 5cbf6d5da8774de9136168b57b55fbc0a6ca9fbf Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Sat, 29 Jun 2024 11:25:12 -0700 Subject: [PATCH 023/107] clippy: update to pulldown-cmark 0.11 --- clippy_lints/src/doc/mod.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 3d875e7ac2d3f..3e210fd153bf5 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -6,10 +6,10 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::Visitable; use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args}; use pulldown_cmark::Event::{ - Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text, + Code, DisplayMath, End, FootnoteReference, HardBreak, Html, InlineHtml, InlineMath, Rule, SoftBreak, Start, TaskListMarker, Text, }; use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph}; -use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options}; +use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options, TagEnd}; use rustc_ast::ast::Attribute; use rustc_data_structures::fx::FxHashSet; use rustc_hir::intravisit::{self, Visitor}; @@ -659,7 +659,7 @@ fn check_doc<'a, Events: Iterator, Range { + Html(tag) | InlineHtml(tag) => { if tag.starts_with(", Range { + Start(BlockQuote(_)) => { blockquote_level += 1; containers.push(Container::Blockquote); }, - End(BlockQuote) => { + End(TagEnd::BlockQuote) => { blockquote_level -= 1; containers.pop(); }, @@ -699,15 +699,15 @@ fn check_doc<'a, Events: Iterator, Range { + End(TagEnd::CodeBlock) => { in_code = false; is_rust = false; ignore = false; }, - Start(Link(_, url, _)) => in_link = Some(url), - End(Link(..)) => in_link = None, - Start(Heading(_, _, _) | Paragraph | Item) => { - if let Start(Heading(_, _, _)) = event { + Start(Link { dest_url, .. }) => in_link = Some(dest_url), + End(TagEnd::Link) => in_link = None, + Start(Heading { .. } | Paragraph | Item) => { + if let Start(Heading { .. }) = event { in_heading = true; } if let Start(Item) = event { @@ -720,11 +720,11 @@ fn check_doc<'a, Events: Iterator, Range { - if let End(Heading(_, _, _)) = event { + End(TagEnd::Heading(_) | TagEnd::Paragraph | TagEnd::Item) => { + if let End(TagEnd::Heading(_)) = event { in_heading = false; } - if let End(Item) = event { + if let End(TagEnd::Item) = event { containers.pop(); } if ticks_unbalanced && let Some(span) = fragments.span(cx, paragraph_range.clone()) { @@ -746,8 +746,8 @@ fn check_doc<'a, Events: Iterator, Range in_footnote_definition = true, - End(FootnoteDefinition(..)) => in_footnote_definition = false, - Start(_tag) | End(_tag) => (), // We don't care about other tags + End(TagEnd::FootnoteDefinition) => in_footnote_definition = false, + Start(_) | End(_) => (), // We don't care about other tags SoftBreak | HardBreak => { if !containers.is_empty() && let Some((next_event, next_range)) = events.peek() @@ -765,7 +765,7 @@ fn check_doc<'a, Events: Iterator, Range (), + TaskListMarker(_) | Code(_) | Rule | InlineMath(..) | DisplayMath(..) => (), FootnoteReference(text) | Text(text) => { paragraph_range.end = range.end; ticks_unbalanced |= text.contains('`') && !in_code; From 76a068be6da0878393473e4929e5a2cab8af0d0b Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Mon, 1 Jul 2024 22:45:56 +0700 Subject: [PATCH 024/107] Skip installing rust-docs with minimal rustup profile --- rust-toolchain | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-toolchain b/rust-toolchain index 72b50d59f7e9a..52e8831eeb3a1 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,4 @@ [toolchain] channel = "nightly-2024-06-27" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] +profile = "minimal" From 567bea29b14e0365bddbd8b5f0ee9f49c8ea7e72 Mon Sep 17 00:00:00 2001 From: Astra Tsai Date: Mon, 1 Jul 2024 18:57:24 -0700 Subject: [PATCH 025/107] Fix `into_iter_without_iter` false positive when the implementation is not within the first `impl` block --- clippy_utils/src/ty.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e5d2056419681..b0c842f985022 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -1336,15 +1336,13 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n .inherent_impls(ty_did) .into_iter() .flatten() - .map(|&did| { + .find_map(|&did| { cx.tcx .associated_items(did) .filter_by_name_unhygienic(method_name) .next() .filter(|item| item.kind == AssocKind::Fn) }) - .next() - .flatten() } else { None } From ba05b764bcb776c0c732d058e0a0d1ac99c5facb Mon Sep 17 00:00:00 2001 From: Astra Tsai Date: Mon, 1 Jul 2024 19:00:10 -0700 Subject: [PATCH 026/107] Add regression test for #12964 --- tests/ui/into_iter_without_iter.rs | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/ui/into_iter_without_iter.rs b/tests/ui/into_iter_without_iter.rs index c8b9076041a31..109259d6975c7 100644 --- a/tests/ui/into_iter_without_iter.rs +++ b/tests/ui/into_iter_without_iter.rs @@ -185,3 +185,42 @@ pub mod issue11635 { } } } + +pub mod issue12964 { + pub struct MyIter<'a, T: 'a> { + iter: std::slice::Iter<'a, T>, + } + + impl<'a, T> Iterator for MyIter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.iter.next() + } + } + + pub struct MyContainer { + inner: Vec, + } + + impl MyContainer {} + + impl MyContainer { + #[must_use] + pub fn iter(&self) -> MyIter<'_, T> { + <&Self as IntoIterator>::into_iter(self) + } + } + + impl<'a, T> IntoIterator for &'a MyContainer { + type Item = &'a T; + + type IntoIter = MyIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + iter: self.inner.as_slice().iter(), + } + } + } +} From 0dd8b27b6b44533357848276e190f684cb36b797 Mon Sep 17 00:00:00 2001 From: Astra Tsai Date: Mon, 1 Jul 2024 19:10:10 -0700 Subject: [PATCH 027/107] Fix formatting --- clippy_utils/src/ty.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index b0c842f985022..ab9f6433e1871 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -1332,17 +1332,13 @@ pub fn deref_chain<'cx, 'tcx>(cx: &'cx LateContext<'tcx>, ty: Ty<'tcx>) -> impl /// If you need this, you should wrap this call in `clippy_utils::ty::deref_chain().any(...)`. pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<&'a AssocItem> { if let Some(ty_did) = ty.ty_adt_def().map(AdtDef::did) { - cx.tcx - .inherent_impls(ty_did) - .into_iter() - .flatten() - .find_map(|&did| { - cx.tcx - .associated_items(did) - .filter_by_name_unhygienic(method_name) - .next() - .filter(|item| item.kind == AssocKind::Fn) - }) + cx.tcx.inherent_impls(ty_did).into_iter().flatten().find_map(|&did| { + cx.tcx + .associated_items(did) + .filter_by_name_unhygienic(method_name) + .next() + .filter(|item| item.kind == AssocKind::Fn) + }) } else { None } From f715bfc344502d0983d351c2cbdfea5373bd6c50 Mon Sep 17 00:00:00 2001 From: hattizai Date: Tue, 2 Jul 2024 11:25:31 +0800 Subject: [PATCH 028/107] chore: remove duplicate words --- clippy_lints/src/empty_with_brackets.rs | 4 ++-- clippy_lints/src/non_copy_const.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 745599b0e57a2..743ec5b9ea7fb 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -16,7 +16,7 @@ declare_clippy_lint! { /// and it may be desirable to do so consistently for style. /// /// However, removing the brackets also introduces a public constant named after the struct, - /// so this is not just a syntactic simplification but an an API change, and adding them back + /// so this is not just a syntactic simplification but an API change, and adding them back /// is a *breaking* API change. /// /// ### Example @@ -44,7 +44,7 @@ declare_clippy_lint! { /// and it may be desirable to do so consistently for style. /// /// However, removing the brackets also introduces a public constant named after the variant, - /// so this is not just a syntactic simplification but an an API change, and adding them back + /// so this is not just a syntactic simplification but an API change, and adding them back /// is a *breaking* API change. /// /// ### Example diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 5cb8e7bfab2a9..964d199bfcb69 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -258,7 +258,7 @@ impl<'tcx> NonCopyConst<'tcx> { // e.g. implementing `has_frozen_variant` described above, and not running this function // when the type doesn't have any frozen variants would be the 'correct' way for the 2nd // case (that actually removes another suboptimal behavior (I won't say 'false positive') where, - // similar to 2., but with the a frozen variant) (e.g. borrowing + // similar to 2., but with a frozen variant) (e.g. borrowing // `borrow_interior_mutable_const::enums::AssocConsts::TO_BE_FROZEN_VARIANT`). // I chose this way because unfrozen enums as assoc consts are rare (or, hopefully, none). matches!(err, ErrorHandled::TooGeneric(..)) From 2da0edbdf10799f3245b08a2c7b41f3bf7890f6d Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 9 Oct 2023 11:48:13 +0200 Subject: [PATCH 029/107] Honor avoid-breaking-exported-api in needless_pass_by_ref_mut Until now, the lint only emitted a warning, when breaking public API. Now it doesn't lint at all when the config value is not set to `false`, bringing it in line with the other lints using this config value. Also ensures that this config value is documented in the lint. --- clippy_config/src/conf.rs | 2 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 10 ++-- .../needless_pass_by_ref_mut/clippy.toml | 1 + .../needless_pass_by_ref_mut.fixed | 10 ++++ .../needless_pass_by_ref_mut.rs | 10 ++++ .../needless_pass_by_ref_mut.stderr | 12 +++++ tests/ui/needless_pass_by_ref_mut.rs | 18 ++++--- tests/ui/needless_pass_by_ref_mut.stderr | 52 ++++++++----------- tests/ui/needless_pass_by_ref_mut2.fixed | 4 +- tests/ui/needless_pass_by_ref_mut2.rs | 4 +- tests/ui/needless_pass_by_ref_mut2.stderr | 15 +++--- 11 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 tests/ui-toml/needless_pass_by_ref_mut/clippy.toml create mode 100644 tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed create mode 100644 tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs create mode 100644 tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 9cc119c68cb12..7f53aad679330 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -263,7 +263,7 @@ define_Conf! { /// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"] /// ``` (arithmetic_side_effects_allowed_unary: FxHashSet = <_>::default()), - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN. + /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS, SINGLE_CALL_FN, NEEDLESS_PASS_BY_REF_MUT. /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 57ba0da533190..13774987b8eca 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -27,7 +27,7 @@ declare_clippy_lint! { /// Check if a `&mut` function argument is actually used mutably. /// /// Be careful if the function is publicly reexported as it would break compatibility with - /// users of this function. + /// users of this function, when the users pass this function as an argument. /// /// ### Why is this bad? /// Less `mut` means less fights with the borrow checker. It can also lead to more @@ -262,8 +262,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { .iter() .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) { - let show_semver_warning = - self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(*fn_def_id); + let is_exported = cx.effective_visibilities.is_exported(*fn_def_id); + if self.avoid_breaking_exported_api && is_exported { + continue; + } let mut is_cfged = None; for input in unused { @@ -284,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), Applicability::Unspecified, ); - if show_semver_warning { + if is_exported { diag.warn("changing this function will impact semver compatibility"); } if *is_cfged { diff --git a/tests/ui-toml/needless_pass_by_ref_mut/clippy.toml b/tests/ui-toml/needless_pass_by_ref_mut/clippy.toml new file mode 100644 index 0000000000000..cda8d17eed44c --- /dev/null +++ b/tests/ui-toml/needless_pass_by_ref_mut/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed new file mode 100644 index 0000000000000..40556ca5410f2 --- /dev/null +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.fixed @@ -0,0 +1,10 @@ +#![warn(clippy::needless_pass_by_ref_mut)] +#![allow(clippy::ptr_arg)] + +// Should warn +pub fn pub_foo(s: &Vec, b: &u32, x: &mut u32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + *x += *b + s.len() as u32; +} + +fn main() {} diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs new file mode 100644 index 0000000000000..bbc63ceb15a3c --- /dev/null +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs @@ -0,0 +1,10 @@ +#![warn(clippy::needless_pass_by_ref_mut)] +#![allow(clippy::ptr_arg)] + +// Should warn +pub fn pub_foo(s: &mut Vec, b: &u32, x: &mut u32) { + //~^ ERROR: this argument is a mutable reference, but not used mutably + *x += *b + s.len() as u32; +} + +fn main() {} diff --git a/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr new file mode 100644 index 0000000000000..c10607bf4bab8 --- /dev/null +++ b/tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.stderr @@ -0,0 +1,12 @@ +error: this argument is a mutable reference, but not used mutably + --> tests/ui-toml/needless_pass_by_ref_mut/needless_pass_by_ref_mut.rs:5:19 + | +LL | pub fn pub_foo(s: &mut Vec, b: &u32, x: &mut u32) { + | ^^^^^^^^^^^^^ help: consider changing to: `&Vec` + | + = warning: changing this function will impact semver compatibility + = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_pass_by_ref_mut)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/needless_pass_by_ref_mut.rs b/tests/ui/needless_pass_by_ref_mut.rs index eee62122fdfda..162ec82aeded2 100644 --- a/tests/ui/needless_pass_by_ref_mut.rs +++ b/tests/ui/needless_pass_by_ref_mut.rs @@ -232,43 +232,48 @@ async fn async_vec2(b: &mut Vec) { } fn non_mut(n: &str) {} //Should warn -pub async fn call_in_closure1(n: &mut str) { +async fn call_in_closure1(n: &mut str) { (|| non_mut(n))() } fn str_mut(str: &mut String) -> bool { str.pop().is_some() } //Should not warn -pub async fn call_in_closure2(str: &mut String) { +async fn call_in_closure2(str: &mut String) { (|| str_mut(str))(); } // Should not warn. -pub async fn closure(n: &mut usize) -> impl '_ + FnMut() { +async fn closure(n: &mut usize) -> impl '_ + FnMut() { || { *n += 1; } } // Should warn. -pub fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize { +fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize { //~^ ERROR: this argument is a mutable reference, but not used mutably || *n + 1 } // Should not warn. -pub async fn closure3(n: &mut usize) { +async fn closure3(n: &mut usize) { (|| *n += 1)(); } // Should warn. -pub async fn closure4(n: &mut usize) { +async fn closure4(n: &mut usize) { //~^ ERROR: this argument is a mutable reference, but not used mutably (|| { let _x = *n + 1; })(); } +// Should not warn: pub +pub fn pub_foo(s: &mut Vec, b: &u32, x: &mut u32) { + *x += *b + s.len() as u32; +} + // Should not warn. async fn _f(v: &mut Vec<()>) { let x = || v.pop(); @@ -365,4 +370,5 @@ fn main() { used_as_path; let _: fn(&mut u32) = passed_as_local; let _ = if v[0] == 0 { ty_unify_1 } else { ty_unify_2 }; + pub_foo(&mut v, &0, &mut u); } diff --git a/tests/ui/needless_pass_by_ref_mut.stderr b/tests/ui/needless_pass_by_ref_mut.stderr index 51e3ba37dede1..f462fa9099ed6 100644 --- a/tests/ui/needless_pass_by_ref_mut.stderr +++ b/tests/ui/needless_pass_by_ref_mut.stderr @@ -108,109 +108,103 @@ LL | async fn inner_async3(x: &mut i32, y: &mut u32) { | ^^^^^^^^ help: consider changing to: `&i32` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:235:34 + --> tests/ui/needless_pass_by_ref_mut.rs:235:30 | -LL | pub async fn call_in_closure1(n: &mut str) { - | ^^^^^^^^ help: consider changing to: `&str` - | - = warning: changing this function will impact semver compatibility +LL | async fn call_in_closure1(n: &mut str) { + | ^^^^^^^^ help: consider changing to: `&str` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:254:20 - | -LL | pub fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize { - | ^^^^^^^^^^ help: consider changing to: `&usize` + --> tests/ui/needless_pass_by_ref_mut.rs:254:16 | - = warning: changing this function will impact semver compatibility +LL | fn closure2(n: &mut usize) -> impl '_ + FnMut() -> usize { + | ^^^^^^^^^^ help: consider changing to: `&usize` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:265:26 - | -LL | pub async fn closure4(n: &mut usize) { - | ^^^^^^^^^^ help: consider changing to: `&usize` + --> tests/ui/needless_pass_by_ref_mut.rs:265:22 | - = warning: changing this function will impact semver compatibility +LL | async fn closure4(n: &mut usize) { + | ^^^^^^^^^^ help: consider changing to: `&usize` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:314:12 + --> tests/ui/needless_pass_by_ref_mut.rs:319:12 | LL | fn bar(&mut self) {} | ^^^^^^^^^ help: consider changing to: `&self` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:316:18 + --> tests/ui/needless_pass_by_ref_mut.rs:321:18 | LL | async fn foo(&mut self, u: &mut i32, v: &mut u32) { | ^^^^^^^^^ help: consider changing to: `&self` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:316:45 + --> tests/ui/needless_pass_by_ref_mut.rs:321:45 | LL | async fn foo(&mut self, u: &mut i32, v: &mut u32) { | ^^^^^^^^ help: consider changing to: `&u32` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:324:46 + --> tests/ui/needless_pass_by_ref_mut.rs:329:46 | LL | async fn foo2(&mut self, u: &mut i32, v: &mut u32) { | ^^^^^^^^ help: consider changing to: `&u32` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:340:18 + --> tests/ui/needless_pass_by_ref_mut.rs:345:18 | LL | fn _empty_tup(x: &mut (())) {} | ^^^^^^^^^ help: consider changing to: `&()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:341:19 + --> tests/ui/needless_pass_by_ref_mut.rs:346:19 | LL | fn _single_tup(x: &mut ((i32,))) {} | ^^^^^^^^^^^^^ help: consider changing to: `&(i32,)` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:342:18 + --> tests/ui/needless_pass_by_ref_mut.rs:347:18 | LL | fn _multi_tup(x: &mut ((i32, u32))) {} | ^^^^^^^^^^^^^^^^^ help: consider changing to: `&(i32, u32)` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:343:11 + --> tests/ui/needless_pass_by_ref_mut.rs:348:11 | LL | fn _fn(x: &mut (fn())) {} | ^^^^^^^^^^^ help: consider changing to: `&fn()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:345:23 + --> tests/ui/needless_pass_by_ref_mut.rs:350:23 | LL | fn _extern_rust_fn(x: &mut extern "Rust" fn()) {} | ^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&extern "Rust" fn()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:346:20 + --> tests/ui/needless_pass_by_ref_mut.rs:351:20 | LL | fn _extern_c_fn(x: &mut extern "C" fn()) {} | ^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&extern "C" fn()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:347:18 + --> tests/ui/needless_pass_by_ref_mut.rs:352:18 | LL | fn _unsafe_fn(x: &mut unsafe fn()) {} | ^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe fn()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:348:25 + --> tests/ui/needless_pass_by_ref_mut.rs:353:25 | LL | fn _unsafe_extern_fn(x: &mut unsafe extern "C" fn()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn()` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:349:20 + --> tests/ui/needless_pass_by_ref_mut.rs:354:20 | LL | fn _fn_with_arg(x: &mut unsafe extern "C" fn(i32)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn(i32)` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut.rs:350:20 + --> tests/ui/needless_pass_by_ref_mut.rs:355:20 | LL | fn _fn_with_ret(x: &mut unsafe extern "C" fn() -> (i32)) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing to: `&unsafe extern "C" fn() -> (i32)` diff --git a/tests/ui/needless_pass_by_ref_mut2.fixed b/tests/ui/needless_pass_by_ref_mut2.fixed index 3c2576213cd77..f26b39ea6a16b 100644 --- a/tests/ui/needless_pass_by_ref_mut2.fixed +++ b/tests/ui/needless_pass_by_ref_mut2.fixed @@ -5,7 +5,7 @@ #![allow(clippy::redundant_closure_call)] #![warn(clippy::needless_pass_by_ref_mut)] -pub async fn inner_async3(x: &i32, y: &mut u32) { +async fn inner_async3(x: &i32, y: &mut u32) { //~^ ERROR: this argument is a mutable reference, but not used mutably async { *y += 1; @@ -13,7 +13,7 @@ pub async fn inner_async3(x: &i32, y: &mut u32) { .await; } -pub async fn inner_async4(u: &mut i32, v: &u32) { +async fn inner_async4(u: &mut i32, v: &u32) { //~^ ERROR: this argument is a mutable reference, but not used mutably async { *u += 1; diff --git a/tests/ui/needless_pass_by_ref_mut2.rs b/tests/ui/needless_pass_by_ref_mut2.rs index 34b0b564deb8e..4220215b1fe9b 100644 --- a/tests/ui/needless_pass_by_ref_mut2.rs +++ b/tests/ui/needless_pass_by_ref_mut2.rs @@ -5,7 +5,7 @@ #![allow(clippy::redundant_closure_call)] #![warn(clippy::needless_pass_by_ref_mut)] -pub async fn inner_async3(x: &mut i32, y: &mut u32) { +async fn inner_async3(x: &mut i32, y: &mut u32) { //~^ ERROR: this argument is a mutable reference, but not used mutably async { *y += 1; @@ -13,7 +13,7 @@ pub async fn inner_async3(x: &mut i32, y: &mut u32) { .await; } -pub async fn inner_async4(u: &mut i32, v: &mut u32) { +async fn inner_async4(u: &mut i32, v: &mut u32) { //~^ ERROR: this argument is a mutable reference, but not used mutably async { *u += 1; diff --git a/tests/ui/needless_pass_by_ref_mut2.stderr b/tests/ui/needless_pass_by_ref_mut2.stderr index c875360322568..1c0136cf5d594 100644 --- a/tests/ui/needless_pass_by_ref_mut2.stderr +++ b/tests/ui/needless_pass_by_ref_mut2.stderr @@ -1,20 +1,17 @@ error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut2.rs:8:30 + --> tests/ui/needless_pass_by_ref_mut2.rs:8:26 | -LL | pub async fn inner_async3(x: &mut i32, y: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&i32` +LL | async fn inner_async3(x: &mut i32, y: &mut u32) { + | ^^^^^^^^ help: consider changing to: `&i32` | - = warning: changing this function will impact semver compatibility = note: `-D clippy::needless-pass-by-ref-mut` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_pass_by_ref_mut)]` error: this argument is a mutable reference, but not used mutably - --> tests/ui/needless_pass_by_ref_mut2.rs:16:43 + --> tests/ui/needless_pass_by_ref_mut2.rs:16:39 | -LL | pub async fn inner_async4(u: &mut i32, v: &mut u32) { - | ^^^^^^^^ help: consider changing to: `&u32` - | - = warning: changing this function will impact semver compatibility +LL | async fn inner_async4(u: &mut i32, v: &mut u32) { + | ^^^^^^^^ help: consider changing to: `&u32` error: aborting due to 2 previous errors From 125c778d6d01824fde3fe96f765a4dd9341af036 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 2 Jul 2024 19:30:30 +0200 Subject: [PATCH 030/107] Move exported check to check_fn to exit early --- clippy_lints/src/needless_pass_by_ref_mut.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 13774987b8eca..5ffd41d78e0cb 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -138,6 +138,10 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { return; } + if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(fn_def_id) { + return; + } + let hir_id = cx.tcx.local_def_id_to_hir_id(fn_def_id); let is_async = match kind { FnKind::ItemFn(.., header) => { @@ -262,11 +266,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { .iter() .filter(|(def_id, _)| !self.used_fn_def_ids.contains(def_id)) { - let is_exported = cx.effective_visibilities.is_exported(*fn_def_id); - if self.avoid_breaking_exported_api && is_exported { - continue; - } - let mut is_cfged = None; for input in unused { // If the argument is never used mutably, we emit the warning. @@ -286,7 +285,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { format!("&{}", snippet(cx, cx.tcx.hir().span(inner_ty.ty.hir_id), "_"),), Applicability::Unspecified, ); - if is_exported { + if cx.effective_visibilities.is_exported(*fn_def_id) { diag.warn("changing this function will impact semver compatibility"); } if *is_cfged { From 70c8579e210d2fc8dae3bd0ae14c2210326eab5f Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 28 Jun 2024 11:00:42 -0700 Subject: [PATCH 031/107] doc_markdown: detect escaped `` ` `` when checking unmatched Add explanatory comment to complex bounds check Format --- clippy_lints/src/doc/mod.rs | 13 ++++++++++++- tests/ui/doc/unbalanced_ticks.rs | 17 +++++++++++++++++ tests/ui/doc/unbalanced_ticks.stderr | 18 +++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 5faa00b7c9718..357ed08c7c71c 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -769,7 +769,18 @@ fn check_doc<'a, Events: Iterator, Range (), FootnoteReference(text) | Text(text) => { paragraph_range.end = range.end; - ticks_unbalanced |= text.contains('`') && !in_code; + let range_ = range.clone(); + ticks_unbalanced |= text.contains('`') + && !in_code + && doc[range.clone()].bytes().enumerate().any(|(i, c)| { + // scan the markdown source code bytes for backquotes that aren't preceded by backslashes + // - use bytes, instead of chars, to avoid utf8 decoding overhead (special chars are ascii) + // - relevant backquotes are within doc[range], but backslashes are not, because they're not + // actually part of the rendered text (pulldown-cmark doesn't emit any events for escapes) + // - if `range_.start + i == 0`, then `range_.start + i - 1 == -1`, and since we're working in + // usize, that would underflow and maybe panic + c == b'`' && (range_.start + i == 0 || doc.as_bytes().get(range_.start + i - 1) != Some(&b'\\')) + }); if Some(&text) == in_link.as_ref() || ticks_unbalanced { // Probably a link of the form `` // Which are represented as a link to "http://example.com" with diff --git a/tests/ui/doc/unbalanced_ticks.rs b/tests/ui/doc/unbalanced_ticks.rs index 6f7bab72040c8..04446787b6c29 100644 --- a/tests/ui/doc/unbalanced_ticks.rs +++ b/tests/ui/doc/unbalanced_ticks.rs @@ -49,3 +49,20 @@ fn other_markdown() {} /// pub struct Struct; /// ``` fn issue_7421() {} + +/// ` +//~^ ERROR: backticks are unbalanced +fn escape_0() {} + +/// Escaped \` backticks don't count. +fn escape_1() {} + +/// Escaped \` \` backticks don't count. +fn escape_2() {} + +/// Escaped \` ` backticks don't count, but unescaped backticks do. +//~^ ERROR: backticks are unbalanced +fn escape_3() {} + +/// Backslashes ` \` within code blocks don't count. +fn escape_4() {} diff --git a/tests/ui/doc/unbalanced_ticks.stderr b/tests/ui/doc/unbalanced_ticks.stderr index 56ef291362318..50324010e97f7 100644 --- a/tests/ui/doc/unbalanced_ticks.stderr +++ b/tests/ui/doc/unbalanced_ticks.stderr @@ -78,5 +78,21 @@ help: try LL | /// - This item needs `backticks_here` | ~~~~~~~~~~~~~~~~ -error: aborting due to 8 previous errors +error: backticks are unbalanced + --> tests/ui/doc/unbalanced_ticks.rs:53:5 + | +LL | /// ` + | ^ + | + = help: a backtick may be missing a pair + +error: backticks are unbalanced + --> tests/ui/doc/unbalanced_ticks.rs:63:5 + | +LL | /// Escaped \` ` backticks don't count, but unescaped backticks do. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: a backtick may be missing a pair + +error: aborting due to 10 previous errors From cd6023180f131134440fdb2b265c496581d67895 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Tue, 2 Jul 2024 15:55:17 -0400 Subject: [PATCH 032/107] Instance::resolve -> Instance::try_resolve, and other nits --- clippy_lints/src/assigning_clones.rs | 4 ++-- clippy_lints/src/non_copy_const.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 05ea74b0d534f..406f38f411ecd 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -103,7 +103,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option< let args = cx.typeck_results().node_args(expr.hir_id); // If we could not resolve the method, don't apply the lint - let Ok(Some(resolved_method)) = Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args) else { + let Ok(Some(resolved_method)) = Instance::try_resolve(cx.tcx, cx.param_env, fn_def_id, args) else { return None; }; if is_trait_method(cx, expr, sym::Clone) && path.ident.name == sym::clone { @@ -119,7 +119,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option< // If we could not resolve the method, don't apply the lint let Ok(Some(resolved_method)) = (match kind { - ty::FnDef(_, args) => Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args), + ty::FnDef(_, args) => Instance::try_resolve(cx.tcx, cx.param_env, fn_def_id, args), _ => Ok(None), }) else { return None; diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 5cb8e7bfab2a9..b71ebe35eb6a8 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -293,7 +293,7 @@ impl<'tcx> NonCopyConst<'tcx> { ct: ty::UnevaluatedConst<'tcx>, span: Span, ) -> EvalToValTreeResult<'tcx> { - match ty::Instance::resolve(tcx, param_env, ct.def, ct.args) { + match ty::Instance::try_resolve(tcx, param_env, ct.def, ct.args) { Ok(Some(instance)) => { let cid = GlobalId { instance, From ae47b97655d48e7d57f5442b1dfde067b488d4fb Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Wed, 3 Jul 2024 09:52:56 +0200 Subject: [PATCH 033/107] needless-pass-by-ref-mut: Update conf documentation --- book/src/lint_configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 4fc109c7851f7..ad29339a84ad6 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -348,6 +348,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat * [`enum_variant_names`](https://rust-lang.github.io/rust-clippy/master/index.html#enum_variant_names) * [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value) * [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist) +* [`needless_pass_by_ref_mut`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_ref_mut) * [`option_option`](https://rust-lang.github.io/rust-clippy/master/index.html#option_option) * [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer) * [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) From 7b76b947800bac94d041aaf8156450c8e7289da9 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Sat, 22 Jun 2024 06:19:07 +0700 Subject: [PATCH 034/107] add test 12969 and 9841 --- tests/ui/explicit_auto_deref.fixed | 32 +++++++++++++++++++++++++++++ tests/ui/explicit_auto_deref.rs | 32 +++++++++++++++++++++++++++++ tests/ui/explicit_auto_deref.stderr | 14 ++++++++++++- 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index e6ca4bb66ccd3..22bc094965991 100644 --- a/tests/ui/explicit_auto_deref.fixed +++ b/tests/ui/explicit_auto_deref.fixed @@ -345,3 +345,35 @@ fn main() { let _ = &mut ({ *x.u }).x; } } + +mod issue_12969 { + use std::ops::Deref; + + struct Wrapper(T); + + impl Deref for Wrapper { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } + } + + fn foo(_bar: &str) {} + + fn bar() { + let wrapped_bar = Wrapper(""); + + foo(wrapped_bar); + } +} + +mod issue_9841 { + fn takes_array_ref(array: &&[T; N]) { + takes_slice(array) + } + + fn takes_slice(slice: &[T]) { + todo!() + } +} diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs index 7531e1f87b71b..2b3a28f8b6f10 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -345,3 +345,35 @@ fn main() { let _ = &mut ({ *x.u }).x; } } + +mod issue_12969 { + use std::ops::Deref; + + struct Wrapper(T); + + impl Deref for Wrapper { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } + } + + fn foo(_bar: &str) {} + + fn bar() { + let wrapped_bar = Wrapper(""); + + foo(&*wrapped_bar); + } +} + +mod issue_9841 { + fn takes_array_ref(array: &&[T; N]) { + takes_slice(*array) + } + + fn takes_slice(slice: &[T]) { + todo!() + } +} diff --git a/tests/ui/explicit_auto_deref.stderr b/tests/ui/explicit_auto_deref.stderr index 56a183de3487f..4505926736274 100644 --- a/tests/ui/explicit_auto_deref.stderr +++ b/tests/ui/explicit_auto_deref.stderr @@ -271,5 +271,17 @@ error: deref which would be done by auto-deref LL | let _ = &mut (*{ x.u }).x; | ^^^^^^^^^^ help: try: `{ x.u }` -error: aborting due to 45 previous errors +error: deref which would be done by auto-deref + --> tests/ui/explicit_auto_deref.rs:367:13 + | +LL | foo(&*wrapped_bar); + | ^^^^^^^^^^^^^ help: try: `wrapped_bar` + +error: deref which would be done by auto-deref + --> tests/ui/explicit_auto_deref.rs:373:21 + | +LL | takes_slice(*array) + | ^^^^^^ help: try: `array` + +error: aborting due to 47 previous errors From 388de386f5ecbbb0eb7a164fe1fa702db2fa4328 Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Sat, 22 Jun 2024 06:21:35 +0700 Subject: [PATCH 035/107] Make it easier to print debugging with Debug --- clippy_lints/src/dereference.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 3c137b6b70fae..4c1ca75e0dba0 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -175,6 +175,7 @@ struct StateData<'tcx> { adjusted_ty: Ty<'tcx>, } +#[derive(Debug)] struct DerefedBorrow { count: usize, msg: &'static str, @@ -182,6 +183,7 @@ struct DerefedBorrow { for_field_access: Option, } +#[derive(Debug)] enum State { // Any number of deref method calls. DerefMethod { @@ -744,7 +746,7 @@ fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> boo } } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] enum TyCoercionStability { Deref, Reborrow, From c4c41d135227a0a34ddc941ccb6be9296e4cceff Mon Sep 17 00:00:00 2001 From: Lzu Tao Date: Wed, 3 Jul 2024 21:25:43 +0700 Subject: [PATCH 036/107] Fix 12969 and fix 9841 --- clippy_lints/src/dereference.rs | 33 ++++++++++++++++++++--------- clippy_utils/src/lib.rs | 11 ++++++++++ tests/ui/explicit_auto_deref.fixed | 8 +++++-- tests/ui/explicit_auto_deref.rs | 4 ++++ tests/ui/explicit_auto_deref.stderr | 10 ++------- 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 4c1ca75e0dba0..164d51ecb75d1 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -3,7 +3,8 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; use clippy_utils::{ - expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, + expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, peel_middle_ty_refs, DefinedTy, + ExprUseNode, }; use core::mem; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; @@ -1044,16 +1045,28 @@ fn report<'tcx>( return; } - let (prefix, precedence) = if let Some(mutability) = mutability - && !typeck.expr_ty(expr).is_ref() + let ty = typeck.expr_ty(expr); + + // `&&[T; N]`, or `&&..&[T; N]` (src) cannot coerce to `&[T]` (dst). + if let ty::Ref(_, dst, _) = data.adjusted_ty.kind() + && dst.is_slice() { - let prefix = match mutability { - Mutability::Not => "&", - Mutability::Mut => "&mut ", - }; - (prefix, PREC_PREFIX) - } else { - ("", 0) + let (src, n_src_refs) = peel_middle_ty_refs(ty); + if n_src_refs >= 2 && src.is_array() { + return; + } + } + + let (prefix, precedence) = match mutability { + Some(mutability) if !ty.is_ref() => { + let prefix = match mutability { + Mutability::Not => "&", + Mutability::Mut => "&mut ", + }; + (prefix, PREC_PREFIX) + }, + None if !ty.is_ref() && data.adjusted_ty.is_ref() => ("&", 0), + _ => ("", 0), }; span_lint_hir_and_then( cx, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index fda870f435a52..89686916671e9 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2471,6 +2471,17 @@ pub fn peel_hir_ty_refs<'a>(mut ty: &'a hir::Ty<'a>) -> (&'a hir::Ty<'a>, usize) } } +/// Peels off all references on the type. Returns the underlying type and the number of references +/// removed. +pub fn peel_middle_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { + let mut count = 0; + while let rustc_ty::Ref(_, dest_ty, _) = ty.kind() { + ty = *dest_ty; + count += 1; + } + (ty, count) +} + /// Removes `AddrOf` operators (`&`) or deref operators (`*`), but only if a reference type is /// dereferenced. An overloaded deref such as `Vec` to slice would not be removed. pub fn peel_ref_operators<'hir>(cx: &LateContext<'_>, mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> { diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index 22bc094965991..255b2c5a220d8 100644 --- a/tests/ui/explicit_auto_deref.fixed +++ b/tests/ui/explicit_auto_deref.fixed @@ -364,13 +364,17 @@ mod issue_12969 { fn bar() { let wrapped_bar = Wrapper(""); - foo(wrapped_bar); + foo(&wrapped_bar); } } mod issue_9841 { fn takes_array_ref(array: &&[T; N]) { - takes_slice(array) + takes_slice(*array) + } + + fn takes_array_ref_ref(array: &&&[T; N]) { + takes_slice(**array) } fn takes_slice(slice: &[T]) { diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs index 2b3a28f8b6f10..99906999f01d9 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -373,6 +373,10 @@ mod issue_9841 { takes_slice(*array) } + fn takes_array_ref_ref(array: &&&[T; N]) { + takes_slice(**array) + } + fn takes_slice(slice: &[T]) { todo!() } diff --git a/tests/ui/explicit_auto_deref.stderr b/tests/ui/explicit_auto_deref.stderr index 4505926736274..53784934f6386 100644 --- a/tests/ui/explicit_auto_deref.stderr +++ b/tests/ui/explicit_auto_deref.stderr @@ -275,13 +275,7 @@ error: deref which would be done by auto-deref --> tests/ui/explicit_auto_deref.rs:367:13 | LL | foo(&*wrapped_bar); - | ^^^^^^^^^^^^^ help: try: `wrapped_bar` + | ^^^^^^^^^^^^^ help: try: `&wrapped_bar` -error: deref which would be done by auto-deref - --> tests/ui/explicit_auto_deref.rs:373:21 - | -LL | takes_slice(*array) - | ^^^^^^ help: try: `array` - -error: aborting due to 47 previous errors +error: aborting due to 46 previous errors From 0f915f6f30c2fb267e25b0bf14bc4674ba7accca Mon Sep 17 00:00:00 2001 From: Renato Lochetti Date: Fri, 31 May 2024 16:58:19 +0100 Subject: [PATCH 037/107] Add new lint `hashset_insert_after_contains` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + .../src/hashset_insert_after_contains.rs | 137 ++++++++++++++++++ clippy_lints/src/lib.rs | 2 + tests/ui/hashset_insert_after_contains.rs | 77 ++++++++++ tests/ui/hashset_insert_after_contains.stderr | 112 ++++++++++++++ 6 files changed, 330 insertions(+) create mode 100644 clippy_lints/src/hashset_insert_after_contains.rs create mode 100644 tests/ui/hashset_insert_after_contains.rs create mode 100644 tests/ui/hashset_insert_after_contains.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 0974631ac5dc4..3c4a24f2b0bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5401,6 +5401,7 @@ Released 2018-09-13 [`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first [`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len [`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap +[`hashset_insert_after_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#hashset_insert_after_contains [`host_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#host_endian_bytes [`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion [`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 53f346c04e787..ff6ce7b50907f 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -211,6 +211,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::functions::TOO_MANY_ARGUMENTS_INFO, crate::functions::TOO_MANY_LINES_INFO, crate::future_not_send::FUTURE_NOT_SEND_INFO, + crate::hashset_insert_after_contains::HASHSET_INSERT_AFTER_CONTAINS_INFO, crate::if_let_mutex::IF_LET_MUTEX_INFO, crate::if_not_else::IF_NOT_ELSE_INFO, crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO, diff --git a/clippy_lints/src/hashset_insert_after_contains.rs b/clippy_lints/src/hashset_insert_after_contains.rs new file mode 100644 index 0000000000000..a793d6e2eb1a4 --- /dev/null +++ b/clippy_lints/src/hashset_insert_after_contains.rs @@ -0,0 +1,137 @@ +use std::ops::ControlFlow; + +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::{higher, peel_hir_expr_while, SpanlessEq}; +use rustc_hir::{Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::{sym, Span}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `contains` to see if a value is not + /// present on `HashSet` followed by a `insert`. + /// + /// ### Why is this bad? + /// Using just `insert` and checking the returned `bool` is more efficient. + /// + /// ### Example + /// ```rust + /// use std::collections::HashSet; + /// let mut set = HashSet::new(); + /// let value = 5; + /// if !set.contains(&value) { + /// set.insert(value); + /// println!("inserted {value:?}"); + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::collections::HashSet; + /// let mut set = HashSet::new(); + /// let value = 5; + /// if set.insert(&value) { + /// println!("inserted {value:?}"); + /// } + /// ``` + #[clippy::version = "1.80.0"] + pub HASHSET_INSERT_AFTER_CONTAINS, + nursery, + "unnecessary call to `HashSet::contains` followed by `HashSet::insert`" +} + +declare_lint_pass!(HashsetInsertAfterContains => [HASHSET_INSERT_AFTER_CONTAINS]); + +impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if !expr.span.from_expansion() + && let Some(higher::If { + cond: cond_expr, + then: then_expr, + .. + }) = higher::If::hir(expr) + && let Some(contains_expr) = try_parse_contains(cx, cond_expr) + && find_insert_calls(cx, &contains_expr, then_expr) + { + span_lint_and_then( + cx, + HASHSET_INSERT_AFTER_CONTAINS, + expr.span, + "usage of `HashSet::insert` after `HashSet::contains`", + |diag| { + diag.note("`HashSet::insert` returns whether it was inserted") + .span_help(contains_expr.span, "remove the `HashSet::contains` call"); + }, + ); + } + } +} + +struct ContainsExpr<'tcx> { + receiver: &'tcx Expr<'tcx>, + value: &'tcx Expr<'tcx>, + span: Span, +} +fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option> { + let expr = peel_hir_expr_while(expr, |e| { + if let ExprKind::Unary(UnOp::Not, e) = e.kind { + Some(e) + } else { + None + } + }); + if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind { + let value = value.peel_borrows(); + let receiver = receiver.peel_borrows(); + let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); + if value.span.eq_ctxt(expr.span) + && is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) + && path.ident.name == sym!(contains) + { + return Some(ContainsExpr { receiver, value, span }); + } + } + None +} + +struct InsertExpr<'tcx> { + receiver: &'tcx Expr<'tcx>, + value: &'tcx Expr<'tcx>, +} +fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option> { + if let ExprKind::MethodCall(path, receiver, [value], _) = expr.kind { + let value = value.peel_borrows(); + let value = peel_hir_expr_while(value, |e| { + if let ExprKind::Unary(UnOp::Deref, e) = e.kind { + Some(e) + } else { + None + } + }); + + let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); + if is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) && path.ident.name == sym!(insert) { + Some(InsertExpr { receiver, value }) + } else { + None + } + } else { + None + } +} + +fn find_insert_calls<'tcx>(cx: &LateContext<'tcx>, contains_expr: &ContainsExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool { + for_each_expr(expr, |e| { + if let Some(insert_expr) = try_parse_insert(cx, e) + && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver) + && SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fa4c7084f4dd4..fd23659d9c506 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -150,6 +150,7 @@ mod from_raw_with_void_ptr; mod from_str_radix_10; mod functions; mod future_not_send; +mod hashset_insert_after_contains; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; @@ -1172,6 +1173,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); + store.register_late_pass(|_| Box::new(hashset_insert_after_contains::HashsetInsertAfterContains)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/hashset_insert_after_contains.rs b/tests/ui/hashset_insert_after_contains.rs new file mode 100644 index 0000000000000..11e4cde84d64c --- /dev/null +++ b/tests/ui/hashset_insert_after_contains.rs @@ -0,0 +1,77 @@ +#![allow(unused)] +#![allow(clippy::nonminimal_bool)] +#![allow(clippy::needless_borrow)] +#![warn(clippy::hashset_insert_after_contains)] + +use std::collections::HashSet; + +fn main() { + should_warn_cases(); + + should_not_warn_cases(); +} + +fn should_warn_cases() { + let mut set = HashSet::new(); + let value = 5; + + if !set.contains(&value) { + set.insert(value); + println!("Just a comment"); + } + + if set.contains(&value) { + set.insert(value); + println!("Just a comment"); + } + + if !set.contains(&value) { + set.insert(value); + } + + if !!set.contains(&value) { + set.insert(value); + println!("Just a comment"); + } + + if (&set).contains(&value) { + set.insert(value); + } + + let borrow_value = &6; + if !set.contains(borrow_value) { + set.insert(*borrow_value); + } + + let borrow_set = &mut set; + if !borrow_set.contains(&value) { + borrow_set.insert(value); + } +} + +fn should_not_warn_cases() { + let mut set = HashSet::new(); + let value = 5; + let another_value = 6; + + if !set.contains(&value) { + set.insert(another_value); + } + + if !set.contains(&value) { + println!("Just a comment"); + } + + if simply_true() { + set.insert(value); + } + + if !set.contains(&value) { + set.replace(value); //it is not insert + println!("Just a comment"); + } +} + +fn simply_true() -> bool { + true +} diff --git a/tests/ui/hashset_insert_after_contains.stderr b/tests/ui/hashset_insert_after_contains.stderr new file mode 100644 index 0000000000000..d547dfa5c1a39 --- /dev/null +++ b/tests/ui/hashset_insert_after_contains.stderr @@ -0,0 +1,112 @@ +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:18:5 + | +LL | / if !set.contains(&value) { +LL | | set.insert(value); +LL | | println!("Just a comment"); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:18:13 + | +LL | if !set.contains(&value) { + | ^^^^^^^^^^^^^^^^ + = note: `-D clippy::hashset-insert-after-contains` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::hashset_insert_after_contains)]` + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:23:5 + | +LL | / if set.contains(&value) { +LL | | set.insert(value); +LL | | println!("Just a comment"); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:23:12 + | +LL | if set.contains(&value) { + | ^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:28:5 + | +LL | / if !set.contains(&value) { +LL | | set.insert(value); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:28:13 + | +LL | if !set.contains(&value) { + | ^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:32:5 + | +LL | / if !!set.contains(&value) { +LL | | set.insert(value); +LL | | println!("Just a comment"); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:32:14 + | +LL | if !!set.contains(&value) { + | ^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:37:5 + | +LL | / if (&set).contains(&value) { +LL | | set.insert(value); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:37:15 + | +LL | if (&set).contains(&value) { + | ^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:42:5 + | +LL | / if !set.contains(borrow_value) { +LL | | set.insert(*borrow_value); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:42:13 + | +LL | if !set.contains(borrow_value) { + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/hashset_insert_after_contains.rs:47:5 + | +LL | / if !borrow_set.contains(&value) { +LL | | borrow_set.insert(value); +LL | | } + | |_____^ + | + = note: `HashSet::insert` returns whether it was inserted +help: remove the `HashSet::contains` call + --> tests/ui/hashset_insert_after_contains.rs:47:20 + | +LL | if !borrow_set.contains(&value) { + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + From 6661e83e7bf2adcc9a68d59aee6e281f7c051b82 Mon Sep 17 00:00:00 2001 From: Renato Lochetti Date: Mon, 3 Jun 2024 19:04:07 +0100 Subject: [PATCH 038/107] Rename lint, generalize function, add known issues, use multispan --- CHANGELOG.md | 2 +- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 +- ..._contains.rs => set_contains_or_insert.rs} | 78 ++++++------ tests/ui/hashset_insert_after_contains.stderr | 112 ------------------ ..._contains.rs => set_contains_or_insert.rs} | 8 +- tests/ui/set_contains_or_insert.stderr | 61 ++++++++++ 7 files changed, 105 insertions(+), 162 deletions(-) rename clippy_lints/src/{hashset_insert_after_contains.rs => set_contains_or_insert.rs} (60%) delete mode 100644 tests/ui/hashset_insert_after_contains.stderr rename tests/ui/{hashset_insert_after_contains.rs => set_contains_or_insert.rs} (89%) create mode 100644 tests/ui/set_contains_or_insert.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c4a24f2b0bbe..23b3ea9f22137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5401,7 +5401,6 @@ Released 2018-09-13 [`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first [`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len [`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap -[`hashset_insert_after_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#hashset_insert_after_contains [`host_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#host_endian_bytes [`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion [`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op @@ -5799,6 +5798,7 @@ Released 2018-09-13 [`semicolon_outside_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#semicolon_outside_block [`separated_literal_suffix`]: https://rust-lang.github.io/rust-clippy/master/index.html#separated_literal_suffix [`serde_api_misuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#serde_api_misuse +[`set_contains_or_insert`]: https://rust-lang.github.io/rust-clippy/master/index.html#set_contains_or_insert [`shadow_reuse`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_reuse [`shadow_same`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_same [`shadow_unrelated`]: https://rust-lang.github.io/rust-clippy/master/index.html#shadow_unrelated diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ff6ce7b50907f..ef5dfd3ee32c4 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -211,7 +211,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::functions::TOO_MANY_ARGUMENTS_INFO, crate::functions::TOO_MANY_LINES_INFO, crate::future_not_send::FUTURE_NOT_SEND_INFO, - crate::hashset_insert_after_contains::HASHSET_INSERT_AFTER_CONTAINS_INFO, crate::if_let_mutex::IF_LET_MUTEX_INFO, crate::if_not_else::IF_NOT_ELSE_INFO, crate::if_then_some_else_none::IF_THEN_SOME_ELSE_NONE_INFO, @@ -646,6 +645,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::semicolon_block::SEMICOLON_OUTSIDE_BLOCK_INFO, crate::semicolon_if_nothing_returned::SEMICOLON_IF_NOTHING_RETURNED_INFO, crate::serde_api::SERDE_API_MISUSE_INFO, + crate::set_contains_or_insert::SET_CONTAINS_OR_INSERT_INFO, crate::shadow::SHADOW_REUSE_INFO, crate::shadow::SHADOW_SAME_INFO, crate::shadow::SHADOW_UNRELATED_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fd23659d9c506..1747d50855978 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -150,7 +150,6 @@ mod from_raw_with_void_ptr; mod from_str_radix_10; mod functions; mod future_not_send; -mod hashset_insert_after_contains; mod if_let_mutex; mod if_not_else; mod if_then_some_else_none; @@ -319,6 +318,7 @@ mod self_named_constructors; mod semicolon_block; mod semicolon_if_nothing_returned; mod serde_api; +mod set_contains_or_insert; mod shadow; mod significant_drop_tightening; mod single_call_fn; @@ -1173,7 +1173,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); - store.register_late_pass(|_| Box::new(hashset_insert_after_contains::HashsetInsertAfterContains)); + store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/hashset_insert_after_contains.rs b/clippy_lints/src/set_contains_or_insert.rs similarity index 60% rename from clippy_lints/src/hashset_insert_after_contains.rs rename to clippy_lints/src/set_contains_or_insert.rs index a793d6e2eb1a4..d6b6f655ac361 100644 --- a/clippy_lints/src/hashset_insert_after_contains.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -1,12 +1,13 @@ use std::ops::ControlFlow; -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::visitors::for_each_expr; use clippy_utils::{higher, peel_hir_expr_while, SpanlessEq}; use rustc_hir::{Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::symbol::Symbol; use rustc_span::{sym, Span}; declare_clippy_lint! { @@ -17,6 +18,11 @@ declare_clippy_lint! { /// ### Why is this bad? /// Using just `insert` and checking the returned `bool` is more efficient. /// + /// ### Known problems + /// In case the value that wants to be inserted is borrowed and also expensive or impossible + /// to clone. In such scenario, the developer might want to check with `contain` before inserting, + /// to avoid the clone. In this case, it will report a false positive. + /// /// ### Example /// ```rust /// use std::collections::HashSet; @@ -37,12 +43,12 @@ declare_clippy_lint! { /// } /// ``` #[clippy::version = "1.80.0"] - pub HASHSET_INSERT_AFTER_CONTAINS, + pub SET_CONTAINS_OR_INSERT, nursery, - "unnecessary call to `HashSet::contains` followed by `HashSet::insert`" + "call to `HashSet::contains` followed by `HashSet::insert`" } -declare_lint_pass!(HashsetInsertAfterContains => [HASHSET_INSERT_AFTER_CONTAINS]); +declare_lint_pass!(HashsetInsertAfterContains => [SET_CONTAINS_OR_INSERT]); impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { @@ -52,29 +58,26 @@ impl<'tcx> LateLintPass<'tcx> for HashsetInsertAfterContains { then: then_expr, .. }) = higher::If::hir(expr) - && let Some(contains_expr) = try_parse_contains(cx, cond_expr) - && find_insert_calls(cx, &contains_expr, then_expr) + && let Some(contains_expr) = try_parse_op_call(cx, cond_expr, sym!(contains))//try_parse_contains(cx, cond_expr) + && let Some(insert_expr) = find_insert_calls(cx, &contains_expr, then_expr) { - span_lint_and_then( + span_lint( cx, - HASHSET_INSERT_AFTER_CONTAINS, - expr.span, + SET_CONTAINS_OR_INSERT, + vec![contains_expr.span, insert_expr.span], "usage of `HashSet::insert` after `HashSet::contains`", - |diag| { - diag.note("`HashSet::insert` returns whether it was inserted") - .span_help(contains_expr.span, "remove the `HashSet::contains` call"); - }, ); } } } -struct ContainsExpr<'tcx> { +struct OpExpr<'tcx> { receiver: &'tcx Expr<'tcx>, value: &'tcx Expr<'tcx>, span: Span, } -fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option> { + +fn try_parse_op_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, symbol: Symbol) -> Option> { let expr = peel_hir_expr_while(expr, |e| { if let ExprKind::Unary(UnOp::Not, e) = e.kind { Some(e) @@ -82,26 +85,8 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio None } }); - if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind { - let value = value.peel_borrows(); - let receiver = receiver.peel_borrows(); - let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); - if value.span.eq_ctxt(expr.span) - && is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) - && path.ident.name == sym!(contains) - { - return Some(ContainsExpr { receiver, value, span }); - } - } - None -} -struct InsertExpr<'tcx> { - receiver: &'tcx Expr<'tcx>, - value: &'tcx Expr<'tcx>, -} -fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option> { - if let ExprKind::MethodCall(path, receiver, [value], _) = expr.kind { + if let ExprKind::MethodCall(path, receiver, [value], span) = expr.kind { let value = value.peel_borrows(); let value = peel_hir_expr_while(value, |e| { if let ExprKind::Unary(UnOp::Deref, e) = e.kind { @@ -110,28 +95,31 @@ fn try_parse_insert<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Optio None } }); - + let receiver = receiver.peel_borrows(); let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs(); - if is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) && path.ident.name == sym!(insert) { - Some(InsertExpr { receiver, value }) - } else { - None + if value.span.eq_ctxt(expr.span) + && is_type_diagnostic_item(cx, receiver_ty, sym::HashSet) + && path.ident.name == symbol + { + return Some(OpExpr { receiver, value, span }); } - } else { - None } + None } -fn find_insert_calls<'tcx>(cx: &LateContext<'tcx>, contains_expr: &ContainsExpr<'tcx>, expr: &'tcx Expr<'_>) -> bool { +fn find_insert_calls<'tcx>( + cx: &LateContext<'tcx>, + contains_expr: &OpExpr<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option> { for_each_expr(expr, |e| { - if let Some(insert_expr) = try_parse_insert(cx, e) + if let Some(insert_expr) = try_parse_op_call(cx, e, sym!(insert)) && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver) && SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value) { - ControlFlow::Break(()) + ControlFlow::Break(insert_expr) } else { ControlFlow::Continue(()) } }) - .is_some() } diff --git a/tests/ui/hashset_insert_after_contains.stderr b/tests/ui/hashset_insert_after_contains.stderr deleted file mode 100644 index d547dfa5c1a39..0000000000000 --- a/tests/ui/hashset_insert_after_contains.stderr +++ /dev/null @@ -1,112 +0,0 @@ -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:18:5 - | -LL | / if !set.contains(&value) { -LL | | set.insert(value); -LL | | println!("Just a comment"); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:18:13 - | -LL | if !set.contains(&value) { - | ^^^^^^^^^^^^^^^^ - = note: `-D clippy::hashset-insert-after-contains` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::hashset_insert_after_contains)]` - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:23:5 - | -LL | / if set.contains(&value) { -LL | | set.insert(value); -LL | | println!("Just a comment"); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:23:12 - | -LL | if set.contains(&value) { - | ^^^^^^^^^^^^^^^^ - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:28:5 - | -LL | / if !set.contains(&value) { -LL | | set.insert(value); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:28:13 - | -LL | if !set.contains(&value) { - | ^^^^^^^^^^^^^^^^ - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:32:5 - | -LL | / if !!set.contains(&value) { -LL | | set.insert(value); -LL | | println!("Just a comment"); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:32:14 - | -LL | if !!set.contains(&value) { - | ^^^^^^^^^^^^^^^^ - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:37:5 - | -LL | / if (&set).contains(&value) { -LL | | set.insert(value); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:37:15 - | -LL | if (&set).contains(&value) { - | ^^^^^^^^^^^^^^^^ - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:42:5 - | -LL | / if !set.contains(borrow_value) { -LL | | set.insert(*borrow_value); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:42:13 - | -LL | if !set.contains(borrow_value) { - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: usage of `HashSet::insert` after `HashSet::contains` - --> tests/ui/hashset_insert_after_contains.rs:47:5 - | -LL | / if !borrow_set.contains(&value) { -LL | | borrow_set.insert(value); -LL | | } - | |_____^ - | - = note: `HashSet::insert` returns whether it was inserted -help: remove the `HashSet::contains` call - --> tests/ui/hashset_insert_after_contains.rs:47:20 - | -LL | if !borrow_set.contains(&value) { - | ^^^^^^^^^^^^^^^^ - -error: aborting due to 7 previous errors - diff --git a/tests/ui/hashset_insert_after_contains.rs b/tests/ui/set_contains_or_insert.rs similarity index 89% rename from tests/ui/hashset_insert_after_contains.rs rename to tests/ui/set_contains_or_insert.rs index 11e4cde84d64c..8465007402ab0 100644 --- a/tests/ui/hashset_insert_after_contains.rs +++ b/tests/ui/set_contains_or_insert.rs @@ -1,7 +1,7 @@ #![allow(unused)] #![allow(clippy::nonminimal_bool)] #![allow(clippy::needless_borrow)] -#![warn(clippy::hashset_insert_after_contains)] +#![warn(clippy::set_contains_or_insert)] use std::collections::HashSet; @@ -70,6 +70,12 @@ fn should_not_warn_cases() { set.replace(value); //it is not insert println!("Just a comment"); } + + if set.contains(&value) { + println!("value is already in set"); + } else { + set.insert(value); + } } fn simply_true() -> bool { diff --git a/tests/ui/set_contains_or_insert.stderr b/tests/ui/set_contains_or_insert.stderr new file mode 100644 index 0000000000000..507e20964fc20 --- /dev/null +++ b/tests/ui/set_contains_or_insert.stderr @@ -0,0 +1,61 @@ +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:18:13 + | +LL | if !set.contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | set.insert(value); + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::set-contains-or-insert` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::set_contains_or_insert)]` + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:23:12 + | +LL | if set.contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | set.insert(value); + | ^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:28:13 + | +LL | if !set.contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | set.insert(value); + | ^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:32:14 + | +LL | if !!set.contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | set.insert(value); + | ^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:37:15 + | +LL | if (&set).contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | set.insert(value); + | ^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:42:13 + | +LL | if !set.contains(borrow_value) { + | ^^^^^^^^^^^^^^^^^^^^^^ +LL | set.insert(*borrow_value); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: usage of `HashSet::insert` after `HashSet::contains` + --> tests/ui/set_contains_or_insert.rs:47:20 + | +LL | if !borrow_set.contains(&value) { + | ^^^^^^^^^^^^^^^^ +LL | borrow_set.insert(value); + | ^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors + From eff6f68caf08a5c055778150e2154e3061e002a2 Mon Sep 17 00:00:00 2001 From: Renato Lochetti Date: Wed, 3 Jul 2024 19:40:05 +0100 Subject: [PATCH 039/107] Fix typos --- clippy_lints/src/set_contains_or_insert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index d6b6f655ac361..cf8ad0935e8ce 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -20,7 +20,7 @@ declare_clippy_lint! { /// /// ### Known problems /// In case the value that wants to be inserted is borrowed and also expensive or impossible - /// to clone. In such scenario, the developer might want to check with `contain` before inserting, + /// to clone. In such a scenario, the developer might want to check with `contains` before inserting, /// to avoid the clone. In this case, it will report a false positive. /// /// ### Example From 4e71fc4302898e28d7e4c570edf2f3d1feffe116 Mon Sep 17 00:00:00 2001 From: Renato Lochetti Date: Wed, 3 Jul 2024 19:54:02 +0100 Subject: [PATCH 040/107] Small fix after rebase --- clippy_lints/src/set_contains_or_insert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/set_contains_or_insert.rs b/clippy_lints/src/set_contains_or_insert.rs index cf8ad0935e8ce..5e65b9fa5171d 100644 --- a/clippy_lints/src/set_contains_or_insert.rs +++ b/clippy_lints/src/set_contains_or_insert.rs @@ -112,7 +112,7 @@ fn find_insert_calls<'tcx>( contains_expr: &OpExpr<'tcx>, expr: &'tcx Expr<'_>, ) -> Option> { - for_each_expr(expr, |e| { + for_each_expr(cx, expr, |e| { if let Some(insert_expr) = try_parse_op_call(cx, e, sym!(insert)) && SpanlessEq::new(cx).eq_expr(contains_expr.receiver, insert_expr.receiver) && SpanlessEq::new(cx).eq_expr(contains_expr.value, insert_expr.value) From 903874d2f49d615505cb5940c2fd2b12b330d573 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 1 Jul 2024 13:49:16 +0200 Subject: [PATCH 041/107] `needless_return`: Support `#[expect]` on the return statement --- clippy_lints/src/returns.rs | 47 ++++++++++++++++++++++++++------- tests/ui/needless_return.fixed | 35 +++++++++++++++++++++--- tests/ui/needless_return.rs | 35 +++++++++++++++++++++--- tests/ui/needless_return.stderr | 28 ++++++++++---------- 4 files changed, 116 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index c11da3147ef4a..8ced47b48a43d 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -7,6 +7,7 @@ use clippy_utils::{ path_to_local_id, span_contains_cfg, span_find_starting_semi, }; use core::ops::ControlFlow; +use rustc_ast::NestedMetaItem; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::LangItem::ResultErr; @@ -14,13 +15,13 @@ use rustc_hir::{ Block, Body, Expr, ExprKind, FnDecl, HirId, ItemKind, LangItem, MatchSource, Node, OwnerNode, PatKind, QPath, Stmt, StmtKind, }; -use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_lint::{LateContext, LateLintPass, Level, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; -use rustc_span::{BytePos, Pos, Span}; +use rustc_span::{sym, BytePos, Pos, Span}; use std::borrow::Cow; use std::fmt::Display; @@ -80,6 +81,9 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_RETURN, + // This lint requires some special handling in `check_final_expr` for `#[expect]`. + // This handling needs to be updated if the group gets changed. This should also + // be caught by tests. style, "using a return statement like `return expr;` where an expression would suffice" } @@ -91,6 +95,9 @@ declare_clippy_lint! { /// ### Why is this bad? /// The `return` is unnecessary. /// + /// Returns may be used to add attributes to the return expression. Return + /// statements with attributes are therefore be accepted by this lint. + /// /// ### Example /// ```rust,ignore /// fn foo(x: usize) -> Result<(), Box> { @@ -377,13 +384,39 @@ fn check_final_expr<'tcx>( } }; - if !cx.tcx.hir().attrs(expr.hir_id).is_empty() { - return; - } let borrows = inner.map_or(false, |inner| last_statement_borrows(cx, inner)); if borrows { return; } + if ret_span.from_expansion() { + return; + } + + // Returns may be used to turn an expression into a statement in rustc's AST. + // This allows the addition of attributes, like `#[allow]` (See: clippy#9361) + // `#[expect(clippy::needless_return)]` needs to be handled separatly to + // actually fullfil the expectation (clippy::#12998) + match cx.tcx.hir().attrs(expr.hir_id) { + [] => {}, + [attr] => { + if matches!(Level::from_attr(attr), Some(Level::Expect(_))) + && let metas = attr.meta_item_list() + && let Some(lst) = metas + && let [NestedMetaItem::MetaItem(meta_item)] = lst.as_slice() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && matches!( + lint_name.ident.name.as_str(), + "needless_return" | "style" | "all" | "warnings" + ) + { + // This is an expectation of the `needless_return` lint + } else { + return; + } + }, + _ => return, + } emit_return_lint(cx, ret_span, semi_spans, &replacement, expr.hir_id); }, @@ -415,10 +448,6 @@ fn emit_return_lint( replacement: &RetReplacement<'_>, at: HirId, ) { - if ret_span.from_expansion() { - return; - } - span_lint_hir_and_then( cx, NEEDLESS_RETURN, diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 853f685f04c76..fc4129e1db84f 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -228,12 +228,41 @@ fn needless_return_macro() -> String { format!("Hello {}", "world!") } -fn issue_9361() -> i32 { - let n = 1; - #[allow(clippy::arithmetic_side_effects)] +fn issue_9361(n: i32) -> i32 { + #[expect(clippy::arithmetic_side_effects)] return n + n; } +mod issue_12998 { + fn expect_lint() -> i32 { + let x = 1; + + #[expect(clippy::needless_return)] + return x; + } + + fn expect_group() -> i32 { + let x = 1; + + #[expect(clippy::style)] + return x; + } + + fn expect_all() -> i32 { + let x = 1; + + #[expect(clippy::all)] + return x; + } + + fn expect_warnings() -> i32 { + let x = 1; + + #[expect(warnings)] + return x; + } +} + fn issue8336(x: i32) -> bool { if x > 0 { println!("something"); diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index e9c1e0e8ae8ed..61c7a02008f08 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -236,12 +236,41 @@ fn needless_return_macro() -> String { return format!("Hello {}", "world!"); } -fn issue_9361() -> i32 { - let n = 1; - #[allow(clippy::arithmetic_side_effects)] +fn issue_9361(n: i32) -> i32 { + #[expect(clippy::arithmetic_side_effects)] return n + n; } +mod issue_12998 { + fn expect_lint() -> i32 { + let x = 1; + + #[expect(clippy::needless_return)] + return x; + } + + fn expect_group() -> i32 { + let x = 1; + + #[expect(clippy::style)] + return x; + } + + fn expect_all() -> i32 { + let x = 1; + + #[expect(clippy::all)] + return x; + } + + fn expect_warnings() -> i32 { + let x = 1; + + #[expect(warnings)] + return x; + } +} + fn issue8336(x: i32) -> bool { if x > 0 { println!("something"); diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 6c891fe7ad3f8..ea9c230eafd22 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -483,7 +483,7 @@ LL + format!("Hello {}", "world!") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:248:9 + --> tests/ui/needless_return.rs:277:9 | LL | return true; | ^^^^^^^^^^^ @@ -497,7 +497,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:250:9 + --> tests/ui/needless_return.rs:279:9 | LL | return false; | ^^^^^^^^^^^^ @@ -509,7 +509,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:257:13 + --> tests/ui/needless_return.rs:286:13 | LL | return 10; | ^^^^^^^^^ @@ -524,7 +524,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:260:13 + --> tests/ui/needless_return.rs:289:13 | LL | return 100; | ^^^^^^^^^^ @@ -537,7 +537,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:268:9 + --> tests/ui/needless_return.rs:297:9 | LL | return 0; | ^^^^^^^^ @@ -549,7 +549,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:275:13 + --> tests/ui/needless_return.rs:304:13 | LL | return *(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -564,7 +564,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:277:13 + --> tests/ui/needless_return.rs:306:13 | LL | return !*(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -577,7 +577,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:284:20 + --> tests/ui/needless_return.rs:313:20 | LL | let _ = 42; | ____________________^ @@ -594,7 +594,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:291:20 + --> tests/ui/needless_return.rs:320:20 | LL | let _ = 42; return; | ^^^^^^^ @@ -606,7 +606,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:303:9 + --> tests/ui/needless_return.rs:332:9 | LL | return Ok(format!("ok!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -618,7 +618,7 @@ LL + Ok(format!("ok!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:305:9 + --> tests/ui/needless_return.rs:334:9 | LL | return Err(format!("err!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -630,7 +630,7 @@ LL + Err(format!("err!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:311:9 + --> tests/ui/needless_return.rs:340:9 | LL | return if true { 1 } else { 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -642,7 +642,7 @@ LL + if true { 1 } else { 2 } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:315:9 + --> tests/ui/needless_return.rs:344:9 | LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -654,7 +654,7 @@ LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else | error: unneeded `return` statement - --> tests/ui/needless_return.rs:336:5 + --> tests/ui/needless_return.rs:365:5 | LL | return { "a".to_string() } + "b" + { "c" }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 9b7227cf3f2083d258230f50a25a7b65481a3f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 3 Jul 2024 21:03:37 +0000 Subject: [PATCH 042/107] Properly handle removal suggestion rendering Do not leave a `+ ` line with only whitespace. In reality, the user will want to remove the entire line. --- tests/ui/dbg_macro/dbg_macro.stderr | 2 -- tests/ui/dbg_macro/dbg_macro_unfixable.stderr | 1 - tests/ui/manual_split_once.stderr | 10 ---------- tests/ui/significant_drop_tightening.stderr | 2 -- 4 files changed, 15 deletions(-) diff --git a/tests/ui/dbg_macro/dbg_macro.stderr b/tests/ui/dbg_macro/dbg_macro.stderr index 86667701da0f3..7d3c3f7c918e8 100644 --- a/tests/ui/dbg_macro/dbg_macro.stderr +++ b/tests/ui/dbg_macro/dbg_macro.stderr @@ -86,7 +86,6 @@ LL | dbg!(); help: remove the invocation before committing it to a version control system | LL - dbg!(); -LL + | error: the `dbg!` macro is intended as a debugging tool @@ -146,7 +145,6 @@ LL | expand_to_dbg!(); help: remove the invocation before committing it to a version control system | LL - dbg!(); -LL + | error: the `dbg!` macro is intended as a debugging tool diff --git a/tests/ui/dbg_macro/dbg_macro_unfixable.stderr b/tests/ui/dbg_macro/dbg_macro_unfixable.stderr index d21595c2fcd42..16e51f4742e34 100644 --- a/tests/ui/dbg_macro/dbg_macro_unfixable.stderr +++ b/tests/ui/dbg_macro/dbg_macro_unfixable.stderr @@ -9,7 +9,6 @@ LL | dbg!(); help: remove the invocation before committing it to a version control system | LL - dbg!(); -LL + | error: the `dbg!` macro is intended as a debugging tool diff --git a/tests/ui/manual_split_once.stderr b/tests/ui/manual_split_once.stderr index b4e51f473cf4d..c5c9be3ac63df 100644 --- a/tests/ui/manual_split_once.stderr +++ b/tests/ui/manual_split_once.stderr @@ -96,12 +96,10 @@ LL | let (l, r) = "a.b.c".split_once('.').unwrap(); help: remove the `iter` usages | LL - let l = iter.next().unwrap(); -LL + | help: remove the `iter` usages | LL - let r = iter.next().unwrap(); -LL + | error: manual implementation of `split_once` @@ -121,12 +119,10 @@ LL | let (l, r) = "a.b.c".split_once('.')?; help: remove the `iter` usages | LL - let l = iter.next()?; -LL + | help: remove the `iter` usages | LL - let r = iter.next()?; -LL + | error: manual implementation of `rsplit_once` @@ -146,12 +142,10 @@ LL | let (l, r) = "a.b.c".rsplit_once('.').unwrap(); help: remove the `iter` usages | LL - let r = iter.next().unwrap(); -LL + | help: remove the `iter` usages | LL - let l = iter.next().unwrap(); -LL + | error: manual implementation of `rsplit_once` @@ -171,12 +165,10 @@ LL | let (l, r) = "a.b.c".rsplit_once('.')?; help: remove the `iter` usages | LL - let r = iter.next()?; -LL + | help: remove the `iter` usages | LL - let l = iter.next()?; -LL + | error: manual implementation of `split_once` @@ -202,12 +194,10 @@ LL | let (a, b) = "a.b.c".split_once('.').unwrap(); help: remove the `iter` usages | LL - let a = iter.next().unwrap(); -LL + | help: remove the `iter` usages | LL - let b = iter.next().unwrap(); -LL + | error: aborting due to 19 previous errors diff --git a/tests/ui/significant_drop_tightening.stderr b/tests/ui/significant_drop_tightening.stderr index f818a14cbe68b..5fc66279f00e7 100644 --- a/tests/ui/significant_drop_tightening.stderr +++ b/tests/ui/significant_drop_tightening.stderr @@ -64,7 +64,6 @@ LL + let rslt0 = mutex.lock().unwrap().abs(); help: remove separated single usage | LL - let rslt0 = lock.abs(); -LL + | error: temporary with significant `Drop` can be early dropped @@ -88,7 +87,6 @@ LL + mutex.lock().unwrap().clear(); help: remove separated single usage | LL - lock.clear(); -LL + | error: aborting due to 4 previous errors From 378962f861ccf98ea33f41dee30282e1ad1c5feb Mon Sep 17 00:00:00 2001 From: Ruihan Li Date: Mon, 29 Apr 2024 12:39:31 +0800 Subject: [PATCH 043/107] Lint significant drop on `while let` and `if let` --- clippy_lints/src/matches/mod.rs | 13 +- .../matches/significant_drop_in_scrutinee.rs | 111 ++++++++++++++---- tests/ui/significant_drop_in_scrutinee.rs | 26 ++++ tests/ui/significant_drop_in_scrutinee.stderr | 29 ++++- 4 files changed, 149 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index bf7156cc53ec3..22a299ae3d82f 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1019,6 +1019,7 @@ impl_lint_pass!(Matches => [ ]); impl<'tcx> LateLintPass<'tcx> for Matches { + #[expect(clippy::too_many_lines)] fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if is_direct_expn_of(expr.span, "matches").is_none() && in_external_macro(cx.sess(), expr.span) { return; @@ -1037,7 +1038,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { return; } if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) { - significant_drop_in_scrutinee::check(cx, expr, ex, arms, source); + significant_drop_in_scrutinee::check_match(cx, expr, ex, arms, source); } collapsible_match::check_match(cx, arms, &self.msrv); @@ -1084,6 +1085,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { } } 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); + 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 { if self.msrv.meets(msrvs::MATCHES_MACRO) { @@ -1126,8 +1128,13 @@ impl<'tcx> LateLintPass<'tcx> for Matches { ); needless_match::check_if_let(cx, expr, &if_let); } - } else if !from_expansion { - redundant_pattern_match::check(cx, expr); + } else { + if let Some(while_let) = higher::WhileLet::hir(expr) { + significant_drop_in_scrutinee::check_while_let(cx, expr, while_let.let_expr, while_let.if_then); + } + if !from_expansion { + redundant_pattern_match::check(cx, expr); + } } } diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 2f72e59834fa6..9047c9627d9ac 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -16,7 +16,7 @@ use rustc_span::Span; use super::SIGNIFICANT_DROP_IN_SCRUTINEE; -pub(super) fn check<'tcx>( +pub(super) fn check_match<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, scrutinee: &'tcx Expr<'_>, @@ -27,10 +27,89 @@ pub(super) fn check<'tcx>( return; } - let (suggestions, message) = has_significant_drop_in_scrutinee(cx, scrutinee, source); + let scrutinee = match (source, &scrutinee.kind) { + (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, + _ => scrutinee, + }; + + let message = if source == MatchSource::Normal { + "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" + } else { + "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" + }; + + let arms = arms.iter().map(|arm| arm.body).collect::>(); + + check(cx, expr, scrutinee, &arms, message, Suggestion::Emit); +} + +pub(super) fn check_if_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + scrutinee: &'tcx Expr<'_>, + if_then: &'tcx Expr<'_>, + if_else: Option<&'tcx Expr<'_>>, +) { + if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) { + return; + } + + let message = + "temporary with significant `Drop` in `if let` scrutinee will live until the end of the `if let` expression"; + + if let Some(if_else) = if_else { + check(cx, expr, scrutinee, &[if_then, if_else], message, Suggestion::Emit); + } else { + check(cx, expr, scrutinee, &[if_then], message, Suggestion::Emit); + } +} + +pub(super) fn check_while_let<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + scrutinee: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, +) { + if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) { + return; + } + + check( + cx, + expr, + scrutinee, + &[body], + "temporary with significant `Drop` in `while let` scrutinee will live until the end of the `while let` expression", + // Don't emit wrong suggestions: We cannot fix the significant drop in the `while let` scrutinee by simply + // moving it out. We need to change the `while` to a `loop` instead. + Suggestion::DontEmit, + ); +} + +#[derive(Copy, Clone, Debug)] +enum Suggestion { + Emit, + DontEmit, +} + +fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + scrutinee: &'tcx Expr<'_>, + arms: &[&'tcx Expr<'_>], + message: &'static str, + sugg: Suggestion, +) { + let mut helper = SigDropHelper::new(cx); + let suggestions = helper.find_sig_drop(scrutinee); + for found in suggestions { span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { - set_diagnostic(diag, cx, expr, found); + match sugg { + Suggestion::Emit => set_suggestion(diag, cx, expr, found), + Suggestion::DontEmit => (), + } + let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); diag.span_label(s, "temporary lives until here"); for span in has_significant_drop_in_arms(cx, arms) { @@ -41,7 +120,7 @@ pub(super) fn check<'tcx>( } } -fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { +fn set_suggestion<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { let original = snippet(cx, found.found_span, ".."); let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); @@ -79,26 +158,6 @@ fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: & ); } -/// If the expression is an `ExprKind::Match`, check if the scrutinee has a significant drop that -/// may have a surprising lifetime. -fn has_significant_drop_in_scrutinee<'tcx>( - cx: &LateContext<'tcx>, - scrutinee: &'tcx Expr<'tcx>, - source: MatchSource, -) -> (Vec, &'static str) { - let mut helper = SigDropHelper::new(cx); - let scrutinee = match (source, &scrutinee.kind) { - (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, - _ => scrutinee, - }; - let message = if source == MatchSource::Normal { - "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" - } else { - "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" - }; - (helper.find_sig_drop(scrutinee), message) -} - struct SigDropChecker<'a, 'tcx> { seen_types: FxHashSet>, cx: &'a LateContext<'tcx>, @@ -428,10 +487,10 @@ impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> { } } -fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet { +fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr<'_>]) -> FxHashSet { let mut helper = ArmSigDropHelper::new(cx); for arm in arms { - helper.visit_expr(arm.body); + helper.visit_expr(arm); } helper.found_sig_drop_spans } diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 8ee15440ccf01..0db6fbfb7be9b 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -801,4 +801,30 @@ fn should_not_trigger_lint_with_explicit_drop() { } } +fn should_trigger_lint_in_if_let() { + let mutex = Mutex::new(vec![1]); + + if let Some(val) = mutex.lock().unwrap().first().copied() { + //~^ ERROR: temporary with significant `Drop` in `if let` scrutinee will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{}", val); + } + + // Should not trigger lint without the final `copied()`, because we actually hold a reference + // (i.e., the `val`) to the locked data. + if let Some(val) = mutex.lock().unwrap().first() { + println!("{}", val); + }; +} + +fn should_trigger_lint_in_while_let() { + let mutex = Mutex::new(vec![1]); + + while let Some(val) = mutex.lock().unwrap().pop() { + //~^ ERROR: temporary with significant `Drop` in `while let` scrutinee will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{}", val); + } +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 4a483e79d8add..c0c93cd10c027 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -541,5 +541,32 @@ LL ~ let value = mutex.lock().unwrap()[0]; LL ~ for val in [value, 2] { | -error: aborting due to 27 previous errors +error: temporary with significant `Drop` in `if let` scrutinee will live until the end of the `if let` expression + --> tests/ui/significant_drop_in_scrutinee.rs:807:24 + | +LL | if let Some(val) = mutex.lock().unwrap().first().copied() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().first().copied(); +LL ~ if let Some(val) = value { + | + +error: temporary with significant `Drop` in `while let` scrutinee will live until the end of the `while let` expression + --> tests/ui/significant_drop_in_scrutinee.rs:823:27 + | +LL | while let Some(val) = mutex.lock().unwrap().pop() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior + +error: aborting due to 29 previous errors From 3a712238127424ca7e40f900bc40252f46627c2f Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Thu, 4 Jul 2024 15:56:11 +0800 Subject: [PATCH 044/107] [`missing_const_for_fn`]: fix suggestions for fn with abi that requires `const_extern_fn` feature --- clippy_config/src/msrvs.rs | 2 +- clippy_lints/src/missing_const_for_fn.rs | 16 ++++- .../ui/missing_const_for_fn/cant_be_const.rs | 9 +++ .../missing_const_for_fn/could_be_const.fixed | 15 +++++ .../ui/missing_const_for_fn/could_be_const.rs | 15 +++++ .../could_be_const.stderr | 30 ++++++++-- .../could_be_const_with_const_extern_fn.fixed | 14 +++++ .../could_be_const_with_const_extern_fn.rs | 14 +++++ ...could_be_const_with_const_extern_fn.stderr | 59 +++++++++++++++++++ 9 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.fixed create mode 100644 tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs create mode 100644 tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.stderr diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index a5761d3270cd2..78fd6149dc098 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -25,7 +25,7 @@ msrv_aliases! { 1,68,0 { PATH_MAIN_SEPARATOR_STR } 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,63,0 { CLONE_INTO } - 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE } + 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_FN } 1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } 1,56,0 { CONST_FN_UNION } diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index bb0d714a31fd4..44ec1bd08cf33 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -11,6 +11,7 @@ use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::Span; +use rustc_target::spec::abi::Abi; declare_clippy_lint! { /// ### What it does @@ -115,7 +116,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { .iter() .any(|param| matches!(param.kind, GenericParamKind::Const { .. })); - if already_const(header) || has_const_generic_params { + if already_const(header) + || has_const_generic_params + || !could_be_const_with_abi(cx, &self.msrv, header.abi) + { return; } }, @@ -171,3 +175,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { fn already_const(header: hir::FnHeader) -> bool { header.constness == Constness::Const } + +fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: &Msrv, abi: Abi) -> bool { + match abi { + Abi::Rust => true, + // `const extern "C"` was stablized after 1.62.0 + Abi::C { unwind: false } => msrv.meets(msrvs::CONST_EXTERN_FN), + // Rest ABIs are still unstable and need the `const_extern_fn` feature enabled. + _ => cx.tcx.features().const_extern_fn, + } +} diff --git a/tests/ui/missing_const_for_fn/cant_be_const.rs b/tests/ui/missing_const_for_fn/cant_be_const.rs index 2750e0cdf3f70..952ff8283632e 100644 --- a/tests/ui/missing_const_for_fn/cant_be_const.rs +++ b/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -180,4 +180,13 @@ mod msrv { unsafe { *self.1 as usize } } } + + #[clippy::msrv = "1.61"] + extern "C" fn c() {} +} + +mod with_extern { + extern "C-unwind" fn c_unwind() {} + extern "system" fn system() {} + extern "system-unwind" fn system_unwind() {} } diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 921dcf0b16261..572ca52439a6b 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -140,6 +140,21 @@ mod msrv { let bar = Bar { val: 1 }; let _ = unsafe { bar.val }; } + + #[clippy::msrv = "1.62"] + mod with_extern { + const extern "C" fn c() {} + //~^ ERROR: this could be a `const fn` + + #[rustfmt::skip] + const extern fn implicit_c() {} + //~^ ERROR: this could be a `const fn` + + // any item functions in extern block won't trigger this lint + extern "C" { + fn c_in_block(); + } + } } mod issue12677 { diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 58e639cc7fd1f..4d19c4d7a48e4 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -140,6 +140,21 @@ mod msrv { let bar = Bar { val: 1 }; let _ = unsafe { bar.val }; } + + #[clippy::msrv = "1.62"] + mod with_extern { + extern "C" fn c() {} + //~^ ERROR: this could be a `const fn` + + #[rustfmt::skip] + extern fn implicit_c() {} + //~^ ERROR: this could be a `const fn` + + // any item functions in extern block won't trigger this lint + extern "C" { + fn c_in_block(); + } + } } mod issue12677 { diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index 8999af761e314..ab36e0a281a7a 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -200,7 +200,29 @@ LL | const fn union_access_can_be_const() { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:146:9 + | +LL | extern "C" fn c() {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const extern "C" fn c() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:150:9 + | +LL | extern fn implicit_c() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const extern fn implicit_c() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:167:9 | LL | / pub fn new(strings: Vec) -> Self { LL | | Self { strings } @@ -213,7 +235,7 @@ LL | pub const fn new(strings: Vec) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:157:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:172:9 | LL | / pub fn empty() -> Self { LL | | Self { strings: Vec::new() } @@ -226,7 +248,7 @@ LL | pub const fn empty() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:168:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:183:9 | LL | / pub fn new(text: String) -> Self { LL | | let vec = Vec::new(); @@ -239,5 +261,5 @@ help: make the function `const` LL | pub const fn new(text: String) -> Self { | +++++ -error: aborting due to 17 previous errors +error: aborting due to 19 previous errors diff --git a/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.fixed b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.fixed new file mode 100644 index 0000000000000..c103db536ab50 --- /dev/null +++ b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.fixed @@ -0,0 +1,14 @@ +#![warn(clippy::missing_const_for_fn)] +#![allow(unsupported_calling_conventions)] +#![feature(const_extern_fn)] + +const extern "C-unwind" fn c_unwind() {} +//~^ ERROR: this could be a `const fn` +const extern "system" fn system() {} +//~^ ERROR: this could be a `const fn` +const extern "system-unwind" fn system_unwind() {} +//~^ ERROR: this could be a `const fn` +pub const extern "stdcall" fn std_call() {} +//~^ ERROR: this could be a `const fn` +pub const extern "stdcall-unwind" fn std_call_unwind() {} +//~^ ERROR: this could be a `const fn` diff --git a/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs new file mode 100644 index 0000000000000..0f7020ae559a9 --- /dev/null +++ b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs @@ -0,0 +1,14 @@ +#![warn(clippy::missing_const_for_fn)] +#![allow(unsupported_calling_conventions)] +#![feature(const_extern_fn)] + +extern "C-unwind" fn c_unwind() {} +//~^ ERROR: this could be a `const fn` +extern "system" fn system() {} +//~^ ERROR: this could be a `const fn` +extern "system-unwind" fn system_unwind() {} +//~^ ERROR: this could be a `const fn` +pub extern "stdcall" fn std_call() {} +//~^ ERROR: this could be a `const fn` +pub extern "stdcall-unwind" fn std_call_unwind() {} +//~^ ERROR: this could be a `const fn` diff --git a/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.stderr b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.stderr new file mode 100644 index 0000000000000..036094a367b22 --- /dev/null +++ b/tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.stderr @@ -0,0 +1,59 @@ +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs:5:1 + | +LL | extern "C-unwind" fn c_unwind() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-const-for-fn` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_const_for_fn)]` +help: make the function `const` + | +LL | const extern "C-unwind" fn c_unwind() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs:7:1 + | +LL | extern "system" fn system() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const extern "system" fn system() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs:9:1 + | +LL | extern "system-unwind" fn system_unwind() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const extern "system-unwind" fn system_unwind() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs:11:1 + | +LL | pub extern "stdcall" fn std_call() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | pub const extern "stdcall" fn std_call() {} + | +++++ + +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const_with_const_extern_fn.rs:13:1 + | +LL | pub extern "stdcall-unwind" fn std_call_unwind() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | pub const extern "stdcall-unwind" fn std_call_unwind() {} + | +++++ + +error: aborting due to 5 previous errors + From 88c4a224807144b535854b53fbd46e6dc18555a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Wed, 4 Jan 2023 09:31:12 +0100 Subject: [PATCH 045/107] New Lint: `byte_char_slices` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a new lint that checks for potentially harder to read byte char slices: `&[b'a', b'b']` and suggests to replace them with the easier to read `b"ab"` form. Signed-Off-By: Marcel Müller Co-authored-by: Matthias Beyer Use iterator to skip validation Signed-off-by: Marcel Müller Suggested-by: Alex Macleod Convert quote escapes to proper form Signed-off-by: Marcel Müller Add more convertable test cases Signed-off-by: Marcel Müller --- CHANGELOG.md | 1 + clippy_lints/src/byte_char_slices.rs | 80 ++++++++++++++++++++++++++++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + tests/ui/byte_char_slices.fixed | 13 +++++ tests/ui/byte_char_slices.rs | 13 +++++ tests/ui/byte_char_slices.stderr | 38 +++++++++++++ 7 files changed, 148 insertions(+) create mode 100644 clippy_lints/src/byte_char_slices.rs create mode 100644 tests/ui/byte_char_slices.fixed create mode 100644 tests/ui/byte_char_slices.rs create mode 100644 tests/ui/byte_char_slices.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 23b3ea9f22137..d919b521e7155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5236,6 +5236,7 @@ Released 2018-09-13 [`boxed_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#boxed_local [`branches_sharing_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#branches_sharing_code [`builtin_type_shadow`]: https://rust-lang.github.io/rust-clippy/master/index.html#builtin_type_shadow +[`byte_char_slices`]: https://rust-lang.github.io/rust-clippy/master/index.html#byte_char_slices [`bytes_count_to_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_count_to_len [`bytes_nth`]: https://rust-lang.github.io/rust-clippy/master/index.html#bytes_nth [`cargo_common_metadata`]: https://rust-lang.github.io/rust-clippy/master/index.html#cargo_common_metadata diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs new file mode 100644 index 0000000000000..a9fe190f1777e --- /dev/null +++ b/clippy_lints/src/byte_char_slices.rs @@ -0,0 +1,80 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::ast::{BorrowKind, Expr, ExprKind, Mutability}; +use rustc_ast::token::{Lit, LitKind}; +use rustc_errors::Applicability; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for hard to read slices of byte characters, that could be more easily expressed as a + /// byte string. + /// + /// ### Why is this bad? + /// + /// Potentially makes the string harder to read. + /// + /// ### Example + /// ```ignore + /// &[b'H', b'e', b'l', b'l', b'o']; + /// ``` + /// Use instead: + /// ```ignore + /// b"Hello" + /// ``` + #[clippy::version = "1.68.0"] + pub BYTE_CHAR_SLICES, + style, + "hard to read byte char slice" +} +declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); + +impl EarlyLintPass for ByteCharSlice { + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if let Some(slice) = is_byte_char_slices(expr) + && !expr.span.from_expansion() + { + span_lint_and_sugg( + cx, + BYTE_CHAR_SLICES, + expr.span, + "can be more succinctly written as a byte str", + "try", + format!("b\"{slice}\""), + Applicability::MaybeIncorrect, + ); + } + } +} + +fn is_byte_char_slices(expr: &Expr) -> Option { + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind { + match &expr.kind { + ExprKind::Array(members) => { + if members.is_empty() { + return None; + } + + members + .iter() + .map(|member| match &member.kind { + ExprKind::Lit(Lit { + kind: LitKind::Byte, + symbol, + .. + }) => Some(symbol.as_str()), + _ => None, + }) + .map(|maybe_quote| match maybe_quote { + Some("\"") => Some("\\\""), + Some("\\'") => Some("'"), + other => other, + }) + .collect::>() + }, + _ => None, + } + } else { + None + } +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index ef5dfd3ee32c4..50661bf46a0a3 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -73,6 +73,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::booleans::OVERLY_COMPLEX_BOOL_EXPR_INFO, crate::borrow_deref_ref::BORROW_DEREF_REF_INFO, crate::box_default::BOX_DEFAULT_INFO, + crate::byte_char_slices::BYTE_CHAR_SLICES_INFO, crate::cargo::CARGO_COMMON_METADATA_INFO, crate::cargo::LINT_GROUPS_PRIORITY_INFO, crate::cargo::MULTIPLE_CRATE_VERSIONS_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1747d50855978..8a9d3b43fb3ab 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -89,6 +89,7 @@ mod bool_to_int_with_if; mod booleans; mod borrow_deref_ref; mod box_default; +mod byte_char_slices; mod cargo; mod casts; mod checked_conversions; @@ -1174,6 +1175,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(msrv()))); store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); + store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/tests/ui/byte_char_slices.fixed b/tests/ui/byte_char_slices.fixed new file mode 100644 index 0000000000000..d1db58f9363e9 --- /dev/null +++ b/tests/ui/byte_char_slices.fixed @@ -0,0 +1,13 @@ +#![allow(unused)] +#![warn(clippy::byte_char_slices)] + +fn main() { + let bad = b"abc"; + let quotes = b"\"Hi"; + let quotes = b"'Sup"; + let escapes = b"\x42Esc"; + + let good = &[b'a', 0x42]; + let good = [b'a', b'a']; + let good: u8 = [b'a', b'c'].into_iter().sum(); +} diff --git a/tests/ui/byte_char_slices.rs b/tests/ui/byte_char_slices.rs new file mode 100644 index 0000000000000..18648fffceb40 --- /dev/null +++ b/tests/ui/byte_char_slices.rs @@ -0,0 +1,13 @@ +#![allow(unused)] +#![warn(clippy::byte_char_slices)] + +fn main() { + let bad = &[b'a', b'b', b'c']; + let quotes = &[b'"', b'H', b'i']; + let quotes = &[b'\'', b'S', b'u', b'p']; + let escapes = &[b'\x42', b'E', b's', b'c']; + + let good = &[b'a', 0x42]; + let good = vec![b'a', b'a']; + let good: u8 = [b'a', b'c'].into_iter().sum(); +} diff --git a/tests/ui/byte_char_slices.stderr b/tests/ui/byte_char_slices.stderr new file mode 100644 index 0000000000000..4e2b5d8a7329a --- /dev/null +++ b/tests/ui/byte_char_slices.stderr @@ -0,0 +1,38 @@ +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:5:15 + | +LL | let bad = &[b'a', b'b', b'c']; + | ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"` + | + = note: `-D clippy::byte-char-slices` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::byte_char_slices)]` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:6:18 + | +LL | let quotes = &[b'"', b'H', b'i']; + | ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:7:18 + | +LL | let quotes = &[b'\'', b'S', b'u', b'p']; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"` + +error: can be more succinctly written as a byte str + --> tests/ui/byte_char_slices.rs:8:19 + | +LL | let escapes = &[b'\x42', b'E', b's', b'c']; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"` + +error: useless use of `vec!` + --> tests/ui/byte_char_slices.rs:11:16 + | +LL | let good = vec![b'a', b'a']; + | ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']` + | + = note: `-D clippy::useless-vec` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_vec)]` + +error: aborting due to 5 previous errors + From 5b30f6a1a3fbd78eb810c02787ad8af4956de305 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 8 Jun 2024 17:18:28 -0400 Subject: [PATCH 046/107] `almost_complete_range`: Delay suggestion creation. --- clippy_lints/src/almost_complete_range.rs | 104 +++++++++++++--------- 1 file changed, 61 insertions(+), 43 deletions(-) diff --git a/clippy_lints/src/almost_complete_range.rs b/clippy_lints/src/almost_complete_range.rs index 57a5cd8fba818..96e9c949b7500 100644 --- a/clippy_lints/src/almost_complete_range.rs +++ b/clippy_lints/src/almost_complete_range.rs @@ -6,7 +6,6 @@ use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_session::impl_lint_pass; -use rustc_span::Span; declare_clippy_lint! { /// ### What it does @@ -41,61 +40,80 @@ impl AlmostCompleteRange { } impl EarlyLintPass for AlmostCompleteRange { fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) { - if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind { - let ctxt = e.span.ctxt(); - let sugg = if let Some(start) = walk_span_to_context(start.span, ctxt) - && let Some(end) = walk_span_to_context(end.span, ctxt) - && self.msrv.meets(msrvs::RANGE_INCLUSIVE) - { - Some((trim_span(cx.sess().source_map(), start.between(end)), "..=")) - } else { - None - }; - check_range(cx, e.span, start, end, sugg); + if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind + && is_incomplete_range(start, end) + && !in_external_macro(cx.sess(), e.span) + { + span_lint_and_then( + cx, + ALMOST_COMPLETE_RANGE, + e.span, + "almost complete ascii range", + |diag| { + let ctxt = e.span.ctxt(); + if let Some(start) = walk_span_to_context(start.span, ctxt) + && let Some(end) = walk_span_to_context(end.span, ctxt) + && self.msrv.meets(msrvs::RANGE_INCLUSIVE) + { + diag.span_suggestion( + trim_span(cx.sess().source_map(), start.between(end)), + "use an inclusive range", + "..=".to_owned(), + Applicability::MaybeIncorrect, + ); + } + }, + ); } } fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &Pat) { if let PatKind::Range(Some(start), Some(end), kind) = &p.kind && matches!(kind.node, RangeEnd::Excluded) + && is_incomplete_range(start, end) + && !in_external_macro(cx.sess(), p.span) { - let sugg = if self.msrv.meets(msrvs::RANGE_INCLUSIVE) { - "..=" - } else { - "..." - }; - check_range(cx, p.span, start, end, Some((kind.span, sugg))); + span_lint_and_then( + cx, + ALMOST_COMPLETE_RANGE, + p.span, + "almost complete ascii range", + |diag| { + diag.span_suggestion( + kind.span, + "use an inclusive range", + if self.msrv.meets(msrvs::RANGE_INCLUSIVE) { + "..=".to_owned() + } else { + "...".to_owned() + }, + Applicability::MaybeIncorrect, + ); + }, + ); } } extract_msrv_attr!(EarlyContext); } -fn check_range(cx: &EarlyContext<'_>, span: Span, start: &Expr, end: &Expr, sugg: Option<(Span, &str)>) { - if let ExprKind::Lit(start_token_lit) = start.peel_parens().kind - && let ExprKind::Lit(end_token_lit) = end.peel_parens().kind - && matches!( - ( - LitKind::from_token_lit(start_token_lit), - LitKind::from_token_lit(end_token_lit), - ), - ( - Ok(LitKind::Byte(b'a') | LitKind::Char('a')), - Ok(LitKind::Byte(b'z') | LitKind::Char('z')) - ) | ( - Ok(LitKind::Byte(b'A') | LitKind::Char('A')), - Ok(LitKind::Byte(b'Z') | LitKind::Char('Z')), - ) | ( - Ok(LitKind::Byte(b'0') | LitKind::Char('0')), - Ok(LitKind::Byte(b'9') | LitKind::Char('9')), +fn is_incomplete_range(start: &Expr, end: &Expr) -> bool { + match (&start.peel_parens().kind, &end.peel_parens().kind) { + (&ExprKind::Lit(start_lit), &ExprKind::Lit(end_lit)) => { + matches!( + (LitKind::from_token_lit(start_lit), LitKind::from_token_lit(end_lit),), + ( + Ok(LitKind::Byte(b'a') | LitKind::Char('a')), + Ok(LitKind::Byte(b'z') | LitKind::Char('z')) + ) | ( + Ok(LitKind::Byte(b'A') | LitKind::Char('A')), + Ok(LitKind::Byte(b'Z') | LitKind::Char('Z')), + ) | ( + Ok(LitKind::Byte(b'0') | LitKind::Char('0')), + Ok(LitKind::Byte(b'9') | LitKind::Char('9')), + ) ) - ) - && !in_external_macro(cx.sess(), span) - { - span_lint_and_then(cx, ALMOST_COMPLETE_RANGE, span, "almost complete ascii range", |diag| { - if let Some((span, sugg)) = sugg { - diag.span_suggestion(span, "use an inclusive range", sugg, Applicability::MaybeIncorrect); - } - }); + }, + _ => false, } } From d9ab0a70f6fa79a6fb2169bc62e4fa59a689165a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 8 Jun 2024 21:27:46 -0400 Subject: [PATCH 047/107] Refactor `assigning_clones`: * Merge some code paths * Use `find` instead of a loop * Move macro check to after hir pattern matching --- clippy_lints/src/assigning_clones.rs | 496 ++++++++++----------------- 1 file changed, 176 insertions(+), 320 deletions(-) diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index 05ea74b0d534f..84e3eb53cc086 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -1,18 +1,16 @@ use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::HirNode; use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap}; use clippy_utils::sugg::Sugg; -use clippy_utils::{is_trait_method, local_is_initialized, path_to_local}; +use clippy_utils::{is_diag_trait_item, last_path_segment, local_is_initialized, path_to_local}; use rustc_errors::Applicability; use rustc_hir::{self as hir, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir; use rustc_middle::ty::{self, Instance, Mutability}; use rustc_session::impl_lint_pass; -use rustc_span::def_id::DefId; use rustc_span::symbol::sym; -use rustc_span::{ExpnKind, Span, SyntaxContext}; +use rustc_span::{Span, SyntaxContext}; declare_clippy_lint! { /// ### What it does @@ -68,165 +66,82 @@ impl AssigningClones { impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]); impl<'tcx> LateLintPass<'tcx> for AssigningClones { - fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) { - // Do not fire the lint in macros - let ctxt = assign_expr.span().ctxt(); - let expn_data = ctxt.outer_expn_data(); - match expn_data.kind { - ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return, - ExpnKind::Root => {}, - } - - let ExprKind::Assign(lhs, rhs, _span) = assign_expr.kind else { - return; - }; - - let Some(call) = extract_call(cx, rhs) else { - return; - }; - - if is_ok_to_suggest(cx, lhs, &call, &self.msrv) { - suggest(cx, ctxt, assign_expr, lhs, &call); - } - } - - extract_msrv_attr!(LateContext); -} - -// Try to resolve the call to `Clone::clone` or `ToOwned::to_owned`. -fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option> { - let fn_def_id = clippy_utils::fn_def_id(cx, expr)?; - - // Fast paths to only check method calls without arguments or function calls with a single argument - let (target, kind, resolved_method) = match expr.kind { - ExprKind::MethodCall(path, receiver, [], _span) => { - let args = cx.typeck_results().node_args(expr.hir_id); - - // If we could not resolve the method, don't apply the lint - let Ok(Some(resolved_method)) = Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args) else { - return None; - }; - if is_trait_method(cx, expr, sym::Clone) && path.ident.name == sym::clone { - (TargetTrait::Clone, CallKind::MethodCall { receiver }, resolved_method) - } else if is_trait_method(cx, expr, sym::ToOwned) && path.ident.name.as_str() == "to_owned" { - (TargetTrait::ToOwned, CallKind::MethodCall { receiver }, resolved_method) - } else { - return None; + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let ExprKind::Assign(lhs, rhs, _) = e.kind + && let typeck = cx.typeck_results() + && let (call_kind, fn_name, fn_id, fn_arg, fn_gen_args) = match rhs.kind { + ExprKind::Call(f, [arg]) + if let ExprKind::Path(fn_path) = &f.kind + && let Some(id) = typeck.qpath_res(fn_path, f.hir_id).opt_def_id() => + { + (CallKind::Ufcs, last_path_segment(fn_path).ident.name, id, arg, typeck.node_args(f.hir_id)) + }, + ExprKind::MethodCall(name, recv, [], _) if let Some(id) = typeck.type_dependent_def_id(rhs.hir_id) => { + (CallKind::Method, name.ident.name, id, recv, typeck.node_args(rhs.hir_id)) + }, + _ => return, } - }, - ExprKind::Call(function, [arg]) => { - let kind = cx.typeck_results().node_type(function.hir_id).kind(); - - // If we could not resolve the method, don't apply the lint - let Ok(Some(resolved_method)) = (match kind { - ty::FnDef(_, args) => Instance::resolve(cx.tcx, cx.param_env, fn_def_id, args), - _ => Ok(None), - }) else { - return None; - }; - if cx.tcx.is_diagnostic_item(sym::to_owned_method, fn_def_id) { - ( - TargetTrait::ToOwned, - CallKind::FunctionCall { self_arg: arg }, - resolved_method, - ) - } else if let Some(trait_did) = cx.tcx.trait_of_item(fn_def_id) - && cx.tcx.is_diagnostic_item(sym::Clone, trait_did) - { - ( - TargetTrait::Clone, - CallKind::FunctionCall { self_arg: arg }, - resolved_method, - ) - } else { - return None; + && let ctxt = e.span.ctxt() + // Don't lint in macros. + && ctxt.is_root() + && let which_trait = match fn_name { + sym::clone if is_diag_trait_item(cx, fn_id, sym::Clone) => CloneTrait::Clone, + _ if fn_name.as_str() == "to_owned" + && is_diag_trait_item(cx, fn_id, sym::ToOwned) + && self.msrv.meets(msrvs::CLONE_INTO) => + { + CloneTrait::ToOwned + }, + _ => return, } - }, - _ => return None, - }; - - Some(CallCandidate { - span: expr.span, - target, - kind, - method_def_id: resolved_method.def_id(), - }) -} - -// Return true if we find that the called method has a custom implementation and isn't derived or -// provided by default by the corresponding trait. -fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>, msrv: &Msrv) -> bool { - // For calls to .to_owned we suggest using .clone_into(), which was only stablilized in 1.63. - // If the current MSRV is below that, don't suggest the lint. - if !msrv.meets(msrvs::CLONE_INTO) && matches!(call.target, TargetTrait::ToOwned) { - return false; - } - - // If the left-hand side is a local variable, it might be uninitialized at this point. - // In that case we do not want to suggest the lint. - if let Some(local) = path_to_local(lhs) { - // TODO: This check currently bails if the local variable has no initializer. - // That is overly conservative - the lint should fire even if there was no initializer, - // but the variable has been initialized before `lhs` was evaluated. - if !local_is_initialized(cx, local) { - return false; - } - } - - let Some(impl_block) = cx.tcx.impl_of_method(call.method_def_id) else { - return false; - }; - - // If the method implementation comes from #[derive(Clone)], then don't suggest the lint. - // Automatically generated Clone impls do not currently override `clone_from`. - // See e.g. https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 for more details. - if cx.tcx.is_builtin_derived(impl_block) { - return false; - } - - // If the call expression is inside an impl block that contains the method invoked by the - // call expression, we bail out to avoid suggesting something that could result in endless - // recursion. - if let Some(local_block_id) = impl_block.as_local() - && let Some(block) = cx.tcx.hir_node_by_def_id(local_block_id).as_owner() - { - let impl_block_owner = block.def_id(); - if cx - .tcx - .hir() - .parent_id_iter(lhs.hir_id) - .any(|parent| parent.owner == impl_block_owner) + && let Ok(Some(resolved_fn)) = Instance::resolve(cx.tcx, cx.param_env, fn_id, fn_gen_args) + // TODO: This check currently bails if the local variable has no initializer. + // That is overly conservative - the lint should fire even if there was no initializer, + // but the variable has been initialized before `lhs` was evaluated. + && path_to_local(lhs).map_or(true, |lhs| local_is_initialized(cx, lhs)) + && let Some(resolved_impl) = cx.tcx.impl_of_method(resolved_fn.def_id()) + // Derived forms don't implement `clone_from`/`clone_into`. + // See https://github.com/rust-lang/rust/pull/98445#issuecomment-1190681305 + && !cx.tcx.is_builtin_derived(resolved_impl) + // Don't suggest calling a function we're implementing. + && resolved_impl.as_local().map_or(true, |block_id| { + cx.tcx.hir().parent_owner_iter(e.hir_id).all(|(id, _)| id.def_id != block_id) + }) + && let resolved_assoc_items = cx.tcx.associated_items(resolved_impl) + // Only suggest if `clone_from`/`clone_into` is explicitly implemented + && resolved_assoc_items.in_definition_order().any(|assoc| + match which_trait { + CloneTrait::Clone => assoc.name == sym::clone_from, + CloneTrait::ToOwned => assoc.name.as_str() == "clone_into", + } + ) + && !clone_source_borrows_from_dest(cx, lhs, rhs.span) { - return false; + span_lint_and_then( + cx, + ASSIGNING_CLONES, + e.span, + match which_trait { + CloneTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient", + CloneTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient", + }, + |diag| { + let mut app = Applicability::Unspecified; + diag.span_suggestion( + e.span, + match which_trait { + CloneTrait::Clone => "use `clone_from()`", + CloneTrait::ToOwned => "use `clone_into()`", + }, + build_sugg(cx, ctxt, lhs, fn_arg, which_trait, call_kind, &mut app), + app, + ); + }, + ); } } - // Find the function for which we want to check that it is implemented. - let provided_fn = match call.target { - TargetTrait::Clone => cx.tcx.get_diagnostic_item(sym::Clone).and_then(|clone| { - cx.tcx - .provided_trait_methods(clone) - .find(|item| item.name == sym::clone_from) - }), - TargetTrait::ToOwned => cx.tcx.get_diagnostic_item(sym::ToOwned).and_then(|to_owned| { - cx.tcx - .provided_trait_methods(to_owned) - .find(|item| item.name.as_str() == "clone_into") - }), - }; - let Some(provided_fn) = provided_fn else { - return false; - }; - - if clone_source_borrows_from_dest(cx, lhs, call.span) { - return false; - } - - // Now take a look if the impl block defines an implementation for the method that we're interested - // in. If not, then we're using a default implementation, which is not interesting, so we will - // not suggest the lint. - let implemented_fns = cx.tcx.impl_item_implementor_ids(impl_block); - implemented_fns.contains_key(&provided_fn.def_id) + extract_msrv_attr!(LateContext); } /// Checks if the data being cloned borrows from the place that is being assigned to: @@ -239,16 +154,6 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC /// /// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows. fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool { - /// If this basic block only exists to drop a local as part of an assignment, returns its - /// successor. Otherwise returns the basic block that was passed in. - fn skip_drop_block(mir: &mir::Body<'_>, bb: mir::BasicBlock) -> mir::BasicBlock { - if let mir::TerminatorKind::Drop { target, .. } = mir.basic_blocks[bb].terminator().kind { - target - } else { - bb - } - } - let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else { return false; }; @@ -267,172 +172,123 @@ fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_spa // // bb2: // s = s_temp - for bb in mir.basic_blocks.iter() { - let terminator = bb.terminator(); - - // Look for the to_owned/clone call. - if terminator.source_info.span != call_span { - continue; + if let Some(terminator) = mir.basic_blocks.iter() + .map(mir::BasicBlockData::terminator) + .find(|term| term.source_info.span == call_span) + && let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind + && let [source] = &**args + && let mir::Operand::Move(source) = &source.node + && let assign_bb = &mir.basic_blocks[assign_bb] + && let assign_bb = match assign_bb.terminator().kind { + // Skip the drop of the assignment's destination. + mir::TerminatorKind::Drop { target, .. } => &mir.basic_blocks[target], + _ => assign_bb, } - - if let mir::TerminatorKind::Call { ref args, target: Some(assign_bb), .. } = terminator.kind - && let [source] = &**args - && let mir::Operand::Move(source) = &source.node - && let assign_bb = skip_drop_block(mir, assign_bb) - // Skip any storage statements as they are just noise - && let Some(assignment) = mir.basic_blocks[assign_bb].statements - .iter() - .find(|stmt| { - !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_)) - }) - && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind - && let Some(borrowers) = borrow_map.get(&borrowed.local) - && borrowers.contains(source.local) - { - return true; - } - - return false; + // Skip any storage statements as they are just noise + && let Some(assignment) = assign_bb.statements + .iter() + .find(|stmt| { + !matches!(stmt.kind, mir::StatementKind::StorageDead(_) | mir::StatementKind::StorageLive(_)) + }) + && let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind + && let Some(borrowers) = borrow_map.get(&borrowed.local) + { + borrowers.contains(source.local) + } else { + false } - false -} - -fn suggest<'tcx>( - cx: &LateContext<'tcx>, - ctxt: SyntaxContext, - assign_expr: &Expr<'tcx>, - lhs: &Expr<'tcx>, - call: &CallCandidate<'tcx>, -) { - span_lint_and_then(cx, ASSIGNING_CLONES, assign_expr.span, call.message(), |diag| { - let mut applicability = Applicability::Unspecified; - - diag.span_suggestion( - assign_expr.span, - call.suggestion_msg(), - call.suggested_replacement(cx, ctxt, lhs, &mut applicability), - applicability, - ); - }); } -#[derive(Copy, Clone, Debug)] -enum CallKind<'tcx> { - MethodCall { receiver: &'tcx Expr<'tcx> }, - FunctionCall { self_arg: &'tcx Expr<'tcx> }, -} - -#[derive(Copy, Clone, Debug)] -enum TargetTrait { +#[derive(Clone, Copy)] +enum CloneTrait { Clone, ToOwned, } -#[derive(Debug)] -struct CallCandidate<'tcx> { - span: Span, - target: TargetTrait, - kind: CallKind<'tcx>, - // DefId of the called method from an impl block that implements the target trait - method_def_id: DefId, +#[derive(Copy, Clone)] +enum CallKind { + Ufcs, + Method, } -impl<'tcx> CallCandidate<'tcx> { - #[inline] - fn message(&self) -> &'static str { - match self.target { - TargetTrait::Clone => "assigning the result of `Clone::clone()` may be inefficient", - TargetTrait::ToOwned => "assigning the result of `ToOwned::to_owned()` may be inefficient", - } - } - - #[inline] - fn suggestion_msg(&self) -> &'static str { - match self.target { - TargetTrait::Clone => "use `clone_from()`", - TargetTrait::ToOwned => "use `clone_into()`", - } - } - - fn suggested_replacement( - &self, - cx: &LateContext<'tcx>, - ctxt: SyntaxContext, - lhs: &Expr<'tcx>, - applicability: &mut Applicability, - ) -> String { - match self.target { - TargetTrait::Clone => { - match self.kind { - CallKind::MethodCall { receiver } => { - let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", applicability) - } else { - // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` - Sugg::hir_with_applicability(cx, lhs, "_", applicability) - } - .maybe_par(); - - // Determine whether we need to reference the argument to clone_from(). - let clone_receiver_type = cx.typeck_results().expr_ty(receiver); - let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(receiver); - let mut arg_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability); - if clone_receiver_type != clone_receiver_adj_type { - // The receiver may have been a value type, so we need to add an `&` to - // be sure the argument to clone_from will be a reference. - arg_sugg = arg_sugg.addr(); - }; - - format!("{receiver_sugg}.clone_from({arg_sugg})") - }, - CallKind::FunctionCall { self_arg, .. } => { - let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` - Sugg::hir_with_applicability(cx, ref_expr, "_", applicability) - } else { - // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)` - Sugg::hir_with_applicability(cx, lhs, "_", applicability).mut_addr() - }; - // The RHS had to be exactly correct before the call, there is no auto-deref for function calls. - let rhs_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability); - - format!("Clone::clone_from({self_sugg}, {rhs_sugg})") - }, - } - }, - TargetTrait::ToOwned => { - let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { - // `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)` - // `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)` - let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", applicability).maybe_par(); - let inner_type = cx.typeck_results().expr_ty(ref_expr); - // If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it - // deref to a mutable reference. - if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) { - sugg +fn build_sugg<'tcx>( + cx: &LateContext<'tcx>, + ctxt: SyntaxContext, + lhs: &'tcx Expr<'_>, + fn_arg: &'tcx Expr<'_>, + which_trait: CloneTrait, + call_kind: CallKind, + app: &mut Applicability, +) -> String { + match which_trait { + CloneTrait::Clone => { + match call_kind { + CallKind::Method => { + let receiver_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { + // `*lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) } else { - sugg.mut_addr() + // `lhs = self_expr.clone();` -> `lhs.clone_from(self_expr)` + Sugg::hir_with_applicability(cx, lhs, "_", app) } + .maybe_par(); + + // Determine whether we need to reference the argument to clone_from(). + let clone_receiver_type = cx.typeck_results().expr_ty(fn_arg); + let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(fn_arg); + let mut arg_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app); + if clone_receiver_type != clone_receiver_adj_type { + // The receiver may have been a value type, so we need to add an `&` to + // be sure the argument to clone_from will be a reference. + arg_sugg = arg_sugg.addr(); + }; + + format!("{receiver_sugg}.clone_from({arg_sugg})") + }, + CallKind::Ufcs => { + let self_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { + // `*lhs = Clone::clone(self_expr);` -> `Clone::clone_from(lhs, self_expr)` + Sugg::hir_with_applicability(cx, ref_expr, "_", app) + } else { + // `lhs = Clone::clone(self_expr);` -> `Clone::clone_from(&mut lhs, self_expr)` + Sugg::hir_with_applicability(cx, lhs, "_", app).mut_addr() + }; + // The RHS had to be exactly correct before the call, there is no auto-deref for function calls. + let rhs_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app); + + format!("Clone::clone_from({self_sugg}, {rhs_sugg})") + }, + } + }, + CloneTrait::ToOwned => { + let rhs_sugg = if let ExprKind::Unary(hir::UnOp::Deref, ref_expr) = lhs.kind { + // `*lhs = rhs.to_owned()` -> `rhs.clone_into(lhs)` + // `*lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, lhs)` + let sugg = Sugg::hir_with_applicability(cx, ref_expr, "_", app).maybe_par(); + let inner_type = cx.typeck_results().expr_ty(ref_expr); + // If after unwrapping the dereference, the type is not a mutable reference, we add &mut to make it + // deref to a mutable reference. + if matches!(inner_type.kind(), ty::Ref(_, _, Mutability::Mut)) { + sugg } else { - // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)` - // `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)` - Sugg::hir_with_applicability(cx, lhs, "_", applicability) - .maybe_par() - .mut_addr() - }; - - match self.kind { - CallKind::MethodCall { receiver } => { - let receiver_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability); - format!("{receiver_sugg}.clone_into({rhs_sugg})") - }, - CallKind::FunctionCall { self_arg, .. } => { - let self_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability); - format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})") - }, + sugg.mut_addr() } - }, - } + } else { + // `lhs = rhs.to_owned()` -> `rhs.clone_into(&mut lhs)` + // `lhs = ToOwned::to_owned(rhs)` -> `ToOwned::clone_into(rhs, &mut lhs)` + Sugg::hir_with_applicability(cx, lhs, "_", app).maybe_par().mut_addr() + }; + + match call_kind { + CallKind::Method => { + let receiver_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app); + format!("{receiver_sugg}.clone_into({rhs_sugg})") + }, + CallKind::Ufcs => { + let self_sugg = Sugg::hir_with_context(cx, fn_arg, ctxt, "_", app); + format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})") + }, + } + }, } } From 9d8a17794fcec862f8896738cfbb7a8e02c0e111 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sat, 8 Jun 2024 21:55:29 -0400 Subject: [PATCH 048/107] Refactor `bool_to_int_with_if`: * Check hir structure before macro checks * Simplify constant checking --- clippy_lints/src/bool_to_int_with_if.rs | 134 ++++++++++-------------- 1 file changed, 58 insertions(+), 76 deletions(-) diff --git a/clippy_lints/src/bool_to_int_with_if.rs b/clippy_lints/src/bool_to_int_with_if.rs index cfb76cab6dc23..561ca9bd9866d 100644 --- a/clippy_lints/src/bool_to_int_with_if.rs +++ b/clippy_lints/src/bool_to_int_with_if.rs @@ -1,13 +1,11 @@ -use clippy_utils::higher::If; -use rustc_ast::LitKind; -use rustc_hir::{Block, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; - use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::Sugg; -use clippy_utils::{in_constant, is_else_clause, is_integer_literal}; +use clippy_utils::{in_constant, is_else_clause}; +use rustc_ast::LitKind; use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does @@ -47,80 +45,64 @@ declare_clippy_lint! { declare_lint_pass!(BoolToIntWithIf => [BOOL_TO_INT_WITH_IF]); impl<'tcx> LateLintPass<'tcx> for BoolToIntWithIf { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) { - check_if_else(cx, expr); - } - } -} - -fn check_if_else<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if let Some(If { - cond, - then, - r#else: Some(r#else), - }) = If::hir(expr) - && let Some(then_lit) = int_literal(then) - && let Some(else_lit) = int_literal(r#else) - { - let inverted = if is_integer_literal(then_lit, 1) && is_integer_literal(else_lit, 0) { - false - } else if is_integer_literal(then_lit, 0) && is_integer_literal(else_lit, 1) { - true - } else { - // Expression isn't boolean, exit - return; - }; - let mut applicability = Applicability::MachineApplicable; - let snippet = { - let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability); - if inverted { - sugg = !sugg; - } - sugg - }; - - let ty = cx.typeck_results().expr_ty(then_lit); // then and else must be of same type + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let ExprKind::If(cond, then, Some(else_)) = expr.kind + && matches!(cond.kind, ExprKind::DropTemps(_)) + && let Some(then_lit) = as_int_bool_lit(then) + && let Some(else_lit) = as_int_bool_lit(else_) + && then_lit != else_lit + && !expr.span.from_expansion() + && !in_constant(cx, expr.hir_id) + { + let ty = cx.typeck_results().expr_ty(then); + let mut applicability = Applicability::MachineApplicable; + let snippet = { + let mut sugg = Sugg::hir_with_applicability(cx, cond, "..", &mut applicability); + if !then_lit { + sugg = !sugg; + } + sugg + }; + let suggestion = { + let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into()); + // when used in else clause if statement should be wrapped in curly braces + if is_else_clause(cx.tcx, expr) { + s = s.blockify(); + } + s + }; - let suggestion = { - let wrap_in_curly = is_else_clause(cx.tcx, expr); - let mut s = Sugg::NonParen(format!("{ty}::from({snippet})").into()); - if wrap_in_curly { - s = s.blockify(); - } - s - }; // when used in else clause if statement should be wrapped in curly braces + let into_snippet = snippet.clone().maybe_par(); + let as_snippet = snippet.as_ty(ty); - let into_snippet = snippet.clone().maybe_par(); - let as_snippet = snippet.as_ty(ty); - - span_lint_and_then( - cx, - BOOL_TO_INT_WITH_IF, - expr.span, - "boolean to int conversion using if", - |diag| { - diag.span_suggestion(expr.span, "replace with from", suggestion, applicability); - diag.note(format!( - "`{as_snippet}` or `{into_snippet}.into()` can also be valid options" - )); - }, - ); - }; + span_lint_and_then( + cx, + BOOL_TO_INT_WITH_IF, + expr.span, + "boolean to int conversion using if", + |diag| { + diag.span_suggestion(expr.span, "replace with from", suggestion, applicability); + diag.note(format!( + "`{as_snippet}` or `{into_snippet}.into()` can also be valid options" + )); + }, + ); + } + } } -// If block contains only a int literal expression, return literal expression -fn int_literal<'tcx>(expr: &'tcx rustc_hir::Expr<'tcx>) -> Option<&'tcx rustc_hir::Expr<'tcx>> { - if let ExprKind::Block(block, _) = expr.kind - && let Block { - stmts: [], // Shouldn't lint if statements with side effects - expr: Some(expr), - .. - } = block - && let ExprKind::Lit(lit) = &expr.kind - && let LitKind::Int(_, _) = lit.node +fn as_int_bool_lit(e: &Expr<'_>) -> Option { + if let ExprKind::Block(b, _) = e.kind + && b.stmts.is_empty() + && let Some(e) = b.expr + && let ExprKind::Lit(lit) = e.kind + && let LitKind::Int(x, _) = lit.node { - Some(expr) + match x.get() { + 0 => Some(false), + 1 => Some(true), + _ => None, + } } else { None } From ac939ad3a1ba16a798d0b697e5b9b84f8c2b5f5d Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 9 Jun 2024 22:19:35 -0400 Subject: [PATCH 049/107] Refactor `disallowed_script_idents`: Simplify script checking loop. --- clippy_lints/src/disallowed_script_idents.rs | 41 +++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index a995f06fb73dc..5ce11900adf8f 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -82,30 +82,25 @@ impl EarlyLintPass for DisallowedScriptIdents { // Note: `symbol.as_str()` is an expensive operation, thus should not be called // more than once for a single symbol. let symbol_str = symbol.as_str(); - if symbol_str.is_ascii() { - continue; - } - for c in symbol_str.chars() { - // We want to iterate through all the scripts associated with this character - // and check whether at least of one scripts is in the whitelist. - let forbidden_script = c - .script_extension() - .iter() - .find(|script| !self.whitelist.contains(script)); - if let Some(script) = forbidden_script { - span_lint( - cx, - DISALLOWED_SCRIPT_IDENTS, - span, - format!( - "identifier `{symbol_str}` has a Unicode script that is not allowed by configuration: {}", - script.full_name() - ), - ); - // We don't want to spawn warning multiple times over a single identifier. - break; - } + // Check if any character in the symbol is not part of any allowed script. + // Fast path for ascii-only idents. + if !symbol_str.is_ascii() + && let Some(script) = symbol_str.chars().find_map(|c| { + c.script_extension() + .iter() + .find(|script| !self.whitelist.contains(script)) + }) + { + span_lint( + cx, + DISALLOWED_SCRIPT_IDENTS, + span, + format!( + "identifier `{symbol_str}` has a Unicode script that is not allowed by configuration: {}", + script.full_name() + ), + ); } } } From 4c44b4e3c85a1ee3ec1d77f97737aa689234f0b6 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 9 Jun 2024 22:11:47 -0400 Subject: [PATCH 050/107] Remove `is_test_module_or_function` and use `is_in_test` instead. --- clippy_lints/src/disallowed_names.rs | 46 +++++-------------- clippy_lints/src/wildcard_imports.rs | 19 ++------ tests/ui/disallowed_names.rs | 1 + tests/ui/wildcard_imports.fixed | 3 ++ tests/ui/wildcard_imports.rs | 3 ++ tests/ui/wildcard_imports.stderr | 10 ++-- .../wildcard_imports_2021.edition2018.fixed | 3 ++ .../wildcard_imports_2021.edition2018.stderr | 10 ++-- .../wildcard_imports_2021.edition2021.fixed | 3 ++ .../wildcard_imports_2021.edition2021.stderr | 10 ++-- tests/ui/wildcard_imports_2021.rs | 3 ++ 11 files changed, 47 insertions(+), 64 deletions(-) diff --git a/clippy_lints/src/disallowed_names.rs b/clippy_lints/src/disallowed_names.rs index 2afbf184117e1..58809604c373e 100644 --- a/clippy_lints/src/disallowed_names.rs +++ b/clippy_lints/src/disallowed_names.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_test_module_or_function; +use clippy_utils::is_in_test; use rustc_data_structures::fx::FxHashSet; -use rustc_hir::{Item, Pat, PatKind}; +use rustc_hir::{Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -27,52 +27,30 @@ declare_clippy_lint! { #[derive(Clone, Debug)] pub struct DisallowedNames { disallow: FxHashSet, - test_modules_deep: u32, } impl DisallowedNames { pub fn new(disallowed_names: &[String]) -> Self { Self { disallow: disallowed_names.iter().cloned().collect(), - test_modules_deep: 0, } } - - fn in_test_module(&self) -> bool { - self.test_modules_deep != 0 - } } impl_lint_pass!(DisallowedNames => [DISALLOWED_NAMES]); impl<'tcx> LateLintPass<'tcx> for DisallowedNames { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_test_module_or_function(cx.tcx, item) { - self.test_modules_deep = self.test_modules_deep.saturating_add(1); - } - } - fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) { - // Check whether we are under the `test` attribute. - if self.in_test_module() { - return; - } - - if let PatKind::Binding(.., ident, _) = pat.kind { - if self.disallow.contains(&ident.name.to_string()) { - span_lint( - cx, - DISALLOWED_NAMES, - ident.span, - format!("use of a disallowed/placeholder name `{}`", ident.name), - ); - } - } - } - - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_test_module_or_function(cx.tcx, item) { - self.test_modules_deep = self.test_modules_deep.saturating_sub(1); + if let PatKind::Binding(.., ident, _) = pat.kind + && self.disallow.contains(&ident.name.to_string()) + && !is_in_test(cx.tcx, pat.hir_id) + { + span_lint( + cx, + DISALLOWED_NAMES, + ident.span, + format!("use of a disallowed/placeholder name `{}`", ident.name), + ); } } } diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 436f0cb79fb26..a0a60ca887583 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_test_module_or_function; +use clippy_utils::is_in_test; use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -100,7 +100,6 @@ declare_clippy_lint! { #[derive(Default)] pub struct WildcardImports { warn_on_all: bool, - test_modules_deep: u32, allowed_segments: FxHashSet, } @@ -108,7 +107,6 @@ impl WildcardImports { pub fn new(warn_on_all: bool, allowed_wildcard_imports: FxHashSet) -> Self { Self { warn_on_all, - test_modules_deep: 0, allowed_segments: allowed_wildcard_imports, } } @@ -122,15 +120,12 @@ impl LateLintPass<'_> for WildcardImports { return; } - if is_test_module_or_function(cx.tcx, item) { - self.test_modules_deep = self.test_modules_deep.saturating_add(1); - } let module = cx.tcx.parent_module_from_def_id(item.owner_id.def_id); if cx.tcx.visibility(item.owner_id.def_id) != ty::Visibility::Restricted(module.to_def_id()) { return; } if let ItemKind::Use(use_path, UseKind::Glob) = &item.kind - && (self.warn_on_all || !self.check_exceptions(item, use_path.segments)) + && (self.warn_on_all || !self.check_exceptions(cx, item, use_path.segments)) && let used_imports = cx.tcx.names_imported_by_glob_use(item.owner_id.def_id) && !used_imports.is_empty() // Already handled by `unused_imports` && !used_imports.contains(&kw::Underscore) @@ -180,20 +175,14 @@ impl LateLintPass<'_> for WildcardImports { span_lint_and_sugg(cx, lint, span, message, "try", sugg, applicability); } } - - fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - if is_test_module_or_function(cx.tcx, item) { - self.test_modules_deep = self.test_modules_deep.saturating_sub(1); - } - } } impl WildcardImports { - fn check_exceptions(&self, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { + fn check_exceptions(&self, cx: &LateContext<'_>, item: &Item<'_>, segments: &[PathSegment<'_>]) -> bool { item.span.from_expansion() || is_prelude_import(segments) - || (is_super_only_import(segments) && self.test_modules_deep > 0) || is_allowed_via_config(segments, &self.allowed_segments) + || (is_super_only_import(segments) && is_in_test(cx.tcx, item.hir_id())) } } diff --git a/tests/ui/disallowed_names.rs b/tests/ui/disallowed_names.rs index 13c883409bf6e..96531bf8d88ca 100644 --- a/tests/ui/disallowed_names.rs +++ b/tests/ui/disallowed_names.rs @@ -60,6 +60,7 @@ fn issue_1647_ref_mut() { //~^ ERROR: use of a disallowed/placeholder name `quux` } +#[cfg(test)] mod tests { fn issue_7305() { // `disallowed_names` lint should not be triggered inside of the test code. diff --git a/tests/ui/wildcard_imports.fixed b/tests/ui/wildcard_imports.fixed index 6fdd728b9b72e..46890ee9213f1 100644 --- a/tests/ui/wildcard_imports.fixed +++ b/tests/ui/wildcard_imports.fixed @@ -204,6 +204,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass { use super::*; @@ -212,6 +213,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_inside_function { fn with_super_inside_function() { use super::*; @@ -219,6 +221,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_further_inside { fn insidefoo() {} mod inner { diff --git a/tests/ui/wildcard_imports.rs b/tests/ui/wildcard_imports.rs index 20e06d4b36641..1a5586cbb88d9 100644 --- a/tests/ui/wildcard_imports.rs +++ b/tests/ui/wildcard_imports.rs @@ -205,6 +205,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass { use super::*; @@ -213,6 +214,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_inside_function { fn with_super_inside_function() { use super::*; @@ -220,6 +222,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_further_inside { fn insidefoo() {} mod inner { diff --git a/tests/ui/wildcard_imports.stderr b/tests/ui/wildcard_imports.stderr index 0c69d5262c26f..8e88f216394d2 100644 --- a/tests/ui/wildcard_imports.stderr +++ b/tests/ui/wildcard_imports.stderr @@ -106,31 +106,31 @@ LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports.rs:236:17 + --> tests/ui/wildcard_imports.rs:239:17 | LL | use super::*; | ^^^^^^^^ help: try: `super::insidefoo` error: usage of wildcard import - --> tests/ui/wildcard_imports.rs:244:13 + --> tests/ui/wildcard_imports.rs:247:13 | LL | use crate::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports.rs:253:17 + --> tests/ui/wildcard_imports.rs:256:17 | LL | use super::super::*; | ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports.rs:262:13 + --> tests/ui/wildcard_imports.rs:265:13 | LL | use super::super::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports.rs:270:13 + --> tests/ui/wildcard_imports.rs:273:13 | LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` diff --git a/tests/ui/wildcard_imports_2021.edition2018.fixed b/tests/ui/wildcard_imports_2021.edition2018.fixed index 6a9fe007d654d..197dd3b94df09 100644 --- a/tests/ui/wildcard_imports_2021.edition2018.fixed +++ b/tests/ui/wildcard_imports_2021.edition2018.fixed @@ -198,6 +198,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass { use super::*; @@ -206,6 +207,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_inside_function { fn with_super_inside_function() { use super::*; @@ -213,6 +215,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_further_inside { fn insidefoo() {} mod inner { diff --git a/tests/ui/wildcard_imports_2021.edition2018.stderr b/tests/ui/wildcard_imports_2021.edition2018.stderr index 11e0bd377692d..66adacd95dcc1 100644 --- a/tests/ui/wildcard_imports_2021.edition2018.stderr +++ b/tests/ui/wildcard_imports_2021.edition2018.stderr @@ -106,31 +106,31 @@ LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:230:17 + --> tests/ui/wildcard_imports_2021.rs:233:17 | LL | use super::*; | ^^^^^^^^ help: try: `super::insidefoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:238:13 + --> tests/ui/wildcard_imports_2021.rs:241:13 | LL | use crate::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:247:17 + --> tests/ui/wildcard_imports_2021.rs:250:17 | LL | use super::super::*; | ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:256:13 + --> tests/ui/wildcard_imports_2021.rs:259:13 | LL | use super::super::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:264:13 + --> tests/ui/wildcard_imports_2021.rs:267:13 | LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` diff --git a/tests/ui/wildcard_imports_2021.edition2021.fixed b/tests/ui/wildcard_imports_2021.edition2021.fixed index 6a9fe007d654d..197dd3b94df09 100644 --- a/tests/ui/wildcard_imports_2021.edition2021.fixed +++ b/tests/ui/wildcard_imports_2021.edition2021.fixed @@ -198,6 +198,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass { use super::*; @@ -206,6 +207,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_inside_function { fn with_super_inside_function() { use super::*; @@ -213,6 +215,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_further_inside { fn insidefoo() {} mod inner { diff --git a/tests/ui/wildcard_imports_2021.edition2021.stderr b/tests/ui/wildcard_imports_2021.edition2021.stderr index 11e0bd377692d..66adacd95dcc1 100644 --- a/tests/ui/wildcard_imports_2021.edition2021.stderr +++ b/tests/ui/wildcard_imports_2021.edition2021.stderr @@ -106,31 +106,31 @@ LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:230:17 + --> tests/ui/wildcard_imports_2021.rs:233:17 | LL | use super::*; | ^^^^^^^^ help: try: `super::insidefoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:238:13 + --> tests/ui/wildcard_imports_2021.rs:241:13 | LL | use crate::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `crate::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:247:17 + --> tests/ui/wildcard_imports_2021.rs:250:17 | LL | use super::super::*; | ^^^^^^^^^^^^^^^ help: try: `super::super::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:256:13 + --> tests/ui/wildcard_imports_2021.rs:259:13 | LL | use super::super::super_imports::*; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `super::super::super_imports::foofoo` error: usage of wildcard import - --> tests/ui/wildcard_imports_2021.rs:264:13 + --> tests/ui/wildcard_imports_2021.rs:267:13 | LL | use super::*; | ^^^^^^^^ help: try: `super::foofoo` diff --git a/tests/ui/wildcard_imports_2021.rs b/tests/ui/wildcard_imports_2021.rs index 18ebc0f512741..606ff080e774b 100644 --- a/tests/ui/wildcard_imports_2021.rs +++ b/tests/ui/wildcard_imports_2021.rs @@ -199,6 +199,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass { use super::*; @@ -207,6 +208,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_inside_function { fn with_super_inside_function() { use super::*; @@ -214,6 +216,7 @@ mod super_imports { } } + #[cfg(test)] mod test_should_pass_further_inside { fn insidefoo() {} mod inner { From 60af2585f74b889da6be05a379c8dad5e0897cf9 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 9 Jun 2024 16:26:26 -0400 Subject: [PATCH 051/107] Refactor `disallowed_methods`: * Simplify `def_id` extraction. * Use the span of the method name instead of the call. --- clippy_lints/src/disallowed_methods.rs | 42 +++++++++---------- tests/ui-internal/disallow_span_lint.stderr | 16 +++---- .../conf_disallowed_methods.stderr | 36 ++++++++-------- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/clippy_lints/src/disallowed_methods.rs b/clippy_lints/src/disallowed_methods.rs index 9de879604e2ec..38fe687f7ccfe 100644 --- a/clippy_lints/src/disallowed_methods.rs +++ b/clippy_lints/src/disallowed_methods.rs @@ -1,6 +1,6 @@ use clippy_config::types::DisallowedPath; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::{fn_def_id, get_parent_expr, path_def_id}; +use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefIdMap; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -83,26 +83,26 @@ impl<'tcx> LateLintPass<'tcx> for DisallowedMethods { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let uncalled_path = if let Some(parent) = get_parent_expr(cx, expr) - && let ExprKind::Call(receiver, _) = parent.kind - && receiver.hir_id == expr.hir_id - { - None - } else { - path_def_id(cx, expr) + let (id, span) = match &expr.kind { + ExprKind::Path(path) + if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = + cx.qpath_res(path, expr.hir_id) => + { + (id, expr.span) + }, + ExprKind::MethodCall(name, ..) if let Some(id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) => { + (id, name.ident.span) + }, + _ => return, }; - let Some(def_id) = uncalled_path.or_else(|| fn_def_id(cx, expr)) else { - return; - }; - let conf = match self.disallowed.get(&def_id) { - Some(&index) => &self.conf_disallowed[index], - None => return, - }; - let msg = format!("use of a disallowed method `{}`", conf.path()); - span_lint_and_then(cx, DISALLOWED_METHODS, expr.span, msg, |diag| { - if let Some(reason) = conf.reason() { - diag.note(reason); - } - }); + if let Some(&index) = self.disallowed.get(&id) { + let conf = &self.conf_disallowed[index]; + let msg = format!("use of a disallowed method `{}`", conf.path()); + span_lint_and_then(cx, DISALLOWED_METHODS, span, msg, |diag| { + if let Some(reason) = conf.reason() { + diag.note(reason); + } + }); + } } } diff --git a/tests/ui-internal/disallow_span_lint.stderr b/tests/ui-internal/disallow_span_lint.stderr index 1be4b665bcbac..66eda44f74556 100644 --- a/tests/ui-internal/disallow_span_lint.stderr +++ b/tests/ui-internal/disallow_span_lint.stderr @@ -1,22 +1,18 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` - --> tests/ui-internal/disallow_span_lint.rs:14:5 + --> tests/ui-internal/disallow_span_lint.rs:14:8 | -LL | / cx.span_lint(lint, span, |lint| { -LL | | lint.primary_message(msg); -LL | | }); - | |______^ +LL | cx.span_lint(lint, span, |lint| { + | ^^^^^^^^^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml) = note: `-D clippy::disallowed-methods` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` - --> tests/ui-internal/disallow_span_lint.rs:20:5 + --> tests/ui-internal/disallow_span_lint.rs:20:9 | -LL | / tcx.node_span_lint(lint, hir_id, span, |lint| { -LL | | lint.primary_message(msg); -LL | | }); - | |______^ +LL | tcx.node_span_lint(lint, hir_id, span, |lint| { + | ^^^^^^^^^^^^^^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml) diff --git a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr index 4afbbf5f80791..f661e76cc746e 100644 --- a/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr +++ b/tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.stderr @@ -2,36 +2,36 @@ error: use of a disallowed method `regex::Regex::new` --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:35:14 | LL | let re = Regex::new(r"ab.*c").unwrap(); - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^ | = note: `-D clippy::disallowed-methods` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` error: use of a disallowed method `regex::Regex::is_match` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:36:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:36:8 | LL | re.is_match("abc"); - | ^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^ | = note: no matching allowed (from clippy.toml) error: use of a disallowed method `std::iter::Iterator::sum` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:39:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:39:14 | LL | a.iter().sum::(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^ error: use of a disallowed method `slice::sort_unstable` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:41:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:41:7 | LL | a.sort_unstable(); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^ error: use of a disallowed method `f32::clamp` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:44:13 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:44:20 | LL | let _ = 2.0f32.clamp(3.0f32, 4.0f32); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^ error: use of a disallowed method `regex::Regex::new` --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:47:61 @@ -55,37 +55,37 @@ error: use of a disallowed method `futures::stream::select_all` --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:54:31 | LL | let same_name_as_module = select_all(vec![empty::<()>()]); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^ error: use of a disallowed method `conf_disallowed_methods::local_fn` --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:56:5 | LL | local_fn(); - | ^^^^^^^^^^ + | ^^^^^^^^ error: use of a disallowed method `conf_disallowed_methods::local_mod::f` --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:57:5 | LL | local_mod::f(); - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ error: use of a disallowed method `conf_disallowed_methods::Struct::method` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:59:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:59:7 | LL | s.method(); - | ^^^^^^^^^^ + | ^^^^^^ error: use of a disallowed method `conf_disallowed_methods::Trait::provided_method` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:60:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:60:7 | LL | s.provided_method(); - | ^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^ error: use of a disallowed method `conf_disallowed_methods::Trait::implemented_method` - --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:61:5 + --> tests/ui-toml/toml_disallowed_methods/conf_disallowed_methods.rs:61:7 | LL | s.implemented_method(); - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^ error: aborting due to 14 previous errors From 6d61bdabeaaf806cc9b45a85fc5231fc6cf2743a Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 5 Jul 2024 02:29:23 -0400 Subject: [PATCH 052/107] Use `is_in_test` in more places. --- clippy_lints/src/functions/impl_trait_in_params.rs | 8 ++++---- clippy_lints/src/incompatible_msrv.rs | 4 ++-- clippy_lints/src/methods/unwrap_expect_used.rs | 4 ++-- clippy_lints/src/missing_assert_message.rs | 4 ++-- clippy_lints/src/write.rs | 5 ++--- clippy_utils/src/lib.rs | 10 ---------- 6 files changed, 12 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/functions/impl_trait_in_params.rs b/clippy_lints/src/functions/impl_trait_in_params.rs index 6fb38a0d6dd84..cf85c74e688d2 100644 --- a/clippy_lints/src/functions/impl_trait_in_params.rs +++ b/clippy_lints/src/functions/impl_trait_in_params.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_in_test_function; +use clippy_utils::is_in_test; use rustc_hir as hir; use rustc_hir::intravisit::FnKind; @@ -41,7 +41,7 @@ fn report(cx: &LateContext<'_>, param: &GenericParam<'_>, generics: &Generics<'_ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: &'tcx Body<'_>, hir_id: HirId) { if let FnKind::ItemFn(_, generics, _) = kind && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() - && !is_in_test_function(cx.tcx, hir_id) + && !is_in_test(cx.tcx, hir_id) { for param in generics.params { if param.is_impl_trait() { @@ -59,7 +59,7 @@ pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) { && of_trait.is_none() && let body = cx.tcx.hir().body(body_id) && cx.tcx.visibility(cx.tcx.hir().body_owner_def_id(body.id())).is_public() - && !is_in_test_function(cx.tcx, impl_item.hir_id()) + && !is_in_test(cx.tcx, impl_item.hir_id()) { for param in impl_item.generics.params { if param.is_impl_trait() { @@ -75,7 +75,7 @@ pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, && let hir::Node::Item(item) = cx.tcx.parent_hir_node(trait_item.hir_id()) // ^^ (Will always be a trait) && !item.vis_span.is_empty() // Is public - && !is_in_test_function(cx.tcx, trait_item.hir_id()) + && !is_in_test(cx.tcx, trait_item.hir_id()) { for param in trait_item.generics.params { if param.is_impl_trait() { diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 35b4481bfee7e..5c63d48adaf0b 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -1,6 +1,6 @@ use clippy_config::msrvs::Msrv; use clippy_utils::diagnostics::span_lint; -use clippy_utils::is_in_test_function; +use clippy_utils::is_in_test; use rustc_attr::{StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{Expr, ExprKind, HirId}; @@ -88,7 +88,7 @@ impl IncompatibleMsrv { return; } let version = self.get_def_id_version(cx.tcx, def_id); - if self.msrv.meets(version) || is_in_test_function(cx.tcx, node) { + if self.msrv.meets(version) || is_in_test(cx.tcx, node) { return; } if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind { diff --git a/clippy_lints/src/methods/unwrap_expect_used.rs b/clippy_lints/src/methods/unwrap_expect_used.rs index 516b8984ad79d..5b0bd0f716ab2 100644 --- a/clippy_lints/src/methods/unwrap_expect_used.rs +++ b/clippy_lints/src/methods/unwrap_expect_used.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::{is_never_like, is_type_diagnostic_item}; -use clippy_utils::{is_in_cfg_test, is_in_test_function, is_lint_allowed}; +use clippy_utils::{is_in_test, is_lint_allowed}; use rustc_hir::Expr; use rustc_lint::{LateContext, Lint}; use rustc_middle::ty; @@ -61,7 +61,7 @@ pub(super) fn check( let method_suffix = if is_err { "_err" } else { "" }; - if allow_unwrap_in_tests && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)) { + if allow_unwrap_in_tests && is_in_test(cx.tcx, expr.hir_id) { return; } diff --git a/clippy_lints/src/missing_assert_message.rs b/clippy_lints/src/missing_assert_message.rs index dd98352da8601..935ed48dacc50 100644 --- a/clippy_lints/src/missing_assert_message.rs +++ b/clippy_lints/src/missing_assert_message.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::is_in_test; use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, PanicExpn}; -use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingAssertMessage { }; // This lint would be very noisy in tests, so just ignore if we're in test context - if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) { + if is_in_test(cx.tcx, expr.hir_id) { return; } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 652ce88bd9516..96e53b7ef0ba7 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::is_in_test; use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall}; use clippy_utils::source::{expand_past_previous_comma, snippet_opt}; -use clippy_utils::{is_in_cfg_test, is_in_test_function}; use rustc_ast::token::LitKind; use rustc_ast::{ FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, @@ -297,8 +297,7 @@ impl<'tcx> LateLintPass<'tcx> for Write { .as_ref() .map_or(false, |crate_name| crate_name == "build_script_build"); - let allowed_in_tests = self.allow_print_in_tests - && (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id)); + let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); match diag_name { sym::print_macro | sym::println_macro if !allowed_in_tests => { if !is_build_script { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 45a31fe442ba7..28d1eeb4e28c5 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -2625,16 +2625,6 @@ pub fn inherits_cfg(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { .any(|attr| attr.has_name(sym::cfg)) } -/// Checks whether item either has `test` attribute applied, or -/// is a module with `test` in its name. -/// -/// Note: Add `//@compile-flags: --test` to UI tests with a `#[test]` function -pub fn is_test_module_or_function(tcx: TyCtxt<'_>, item: &Item<'_>) -> bool { - is_in_test_function(tcx, item.hir_id()) - || matches!(item.kind, ItemKind::Mod(..)) - && item.ident.name.as_str().split('_').any(|a| a == "test" || a == "tests") -} - /// Walks up the HIR tree from the given expression in an attempt to find where the value is /// consumed. /// From 2c09ac3d39c272469b4f9a581f4a5507bcc09ca1 Mon Sep 17 00:00:00 2001 From: Milo Moisson Date: Wed, 12 Jun 2024 18:10:56 +0200 Subject: [PATCH 053/107] feat: add cfg_not_test lint --- CHANGELOG.md | 1 + clippy_lints/src/cfg_not_test.rs | 60 ++++++++++++++++++++++++++++++ clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + 4 files changed, 64 insertions(+) create mode 100644 clippy_lints/src/cfg_not_test.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d919b521e7155..5258cd9f41aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5254,6 +5254,7 @@ Released 2018-09-13 [`cast_sign_loss`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_sign_loss [`cast_slice_different_sizes`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_different_sizes [`cast_slice_from_raw_parts`]: https://rust-lang.github.io/rust-clippy/master/index.html#cast_slice_from_raw_parts +[`cfg_not_test`]: https://rust-lang.github.io/rust-clippy/master/index.html#cfg_not_test [`char_lit_as_u8`]: https://rust-lang.github.io/rust-clippy/master/index.html#char_lit_as_u8 [`chars_last_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_last_cmp [`chars_next_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#chars_next_cmp diff --git a/clippy_lints/src/cfg_not_test.rs b/clippy_lints/src/cfg_not_test.rs new file mode 100644 index 0000000000000..b54f392bf2fad --- /dev/null +++ b/clippy_lints/src/cfg_not_test.rs @@ -0,0 +1,60 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::NestedMetaItem; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `cfg` that excludes code from `test` builds. (i.e., `#{cfg(not(test))]`) + /// + /// ### Why is this bad? + /// This may give the false impression that a codebase has 100% coverage, yet actually has untested code. + /// Enabling this also guards against excessive mockery as well, which is an anti-pattern. + /// + /// ### Example + /// ```rust + /// # fn important_check() {} + /// #[cfg(not(test))] + /// important_check(); // I'm not actually tested, but not including me will falsely increase coverage! + /// ``` + /// Use instead: + /// ```rust + /// # fn important_check() {} + /// important_check(); + /// ``` + #[clippy::version = "1.73.0"] + pub CFG_NOT_TEST, + restriction, + "enforce against excluding code from test builds" +} + +declare_lint_pass!(CfgNotTest => [CFG_NOT_TEST]); + +impl EarlyLintPass for CfgNotTest { + fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &rustc_ast::Attribute) { + if attr.has_name(rustc_span::sym::cfg) && contains_not_test(attr.meta_item_list().as_deref(), false) { + span_lint_and_then( + cx, + CFG_NOT_TEST, + attr.span, + "code is excluded from test builds", + |diag| { + diag.help("consider not excluding any code from test builds"); + diag.note_once("this could increase code coverage despite not actually being tested"); + }, + ); + } + } +} + +fn contains_not_test(list: Option<&[NestedMetaItem]>, not: bool) -> bool { + list.is_some_and(|list| { + list.iter().any(|item| { + item.ident().is_some_and(|ident| match ident.name { + rustc_span::sym::not => contains_not_test(item.meta_item_list(), !not), + rustc_span::sym::test => not, + _ => contains_not_test(item.meta_item_list(), not), + }) + }) + }) +} diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 50661bf46a0a3..0b674a7d7ab3f 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -104,6 +104,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::casts::REF_AS_PTR_INFO, crate::casts::UNNECESSARY_CAST_INFO, crate::casts::ZERO_PTR_INFO, + crate::cfg_not_test::CFG_NOT_TEST_INFO, crate::checked_conversions::CHECKED_CONVERSIONS_INFO, crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO, crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8a9d3b43fb3ab..3d40b397ab9b5 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -92,6 +92,7 @@ mod box_default; mod byte_char_slices; mod cargo; mod casts; +mod cfg_not_test; mod checked_conversions; mod cognitive_complexity; mod collapsible_if; @@ -1176,6 +1177,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); store.register_late_pass(|_| Box::new(set_contains_or_insert::HashsetInsertAfterContains)); store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); + store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); // add lints here, do not remove this comment, it's used in `new_lint` } From dd37441a646c64c89150cc008dacdbb1f68d3f14 Mon Sep 17 00:00:00 2001 From: Milo Moisson Date: Wed, 12 Jun 2024 18:11:01 +0200 Subject: [PATCH 054/107] test: add cfg_not_test tests --- tests/ui/cfg_not_test.rs | 32 +++++++++++++++++++++++++ tests/ui/cfg_not_test.stderr | 45 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/ui/cfg_not_test.rs create mode 100644 tests/ui/cfg_not_test.stderr diff --git a/tests/ui/cfg_not_test.rs b/tests/ui/cfg_not_test.rs new file mode 100644 index 0000000000000..da3e29d289662 --- /dev/null +++ b/tests/ui/cfg_not_test.rs @@ -0,0 +1,32 @@ +#![allow(unused)] +#![warn(clippy::cfg_not_test)] + +fn important_check() {} + +fn main() { + // Statement + #[cfg(not(test))] + let answer = 42; + + // Expression + #[cfg(not(test))] + important_check(); + + // Make sure only not(test) are checked, not other attributes + #[cfg(not(foo))] + important_check(); +} + +#[cfg(not(not(test)))] +struct CfgNotTest; + +// Deeply nested `not(test)` +#[cfg(not(test))] +fn foo() {} +#[cfg(all(debug_assertions, not(test)))] +fn bar() {} +#[cfg(not(any(not(debug_assertions), test)))] +fn baz() {} + +#[cfg(test)] +mod tests {} diff --git a/tests/ui/cfg_not_test.stderr b/tests/ui/cfg_not_test.stderr new file mode 100644 index 0000000000000..c1bf626887afb --- /dev/null +++ b/tests/ui/cfg_not_test.stderr @@ -0,0 +1,45 @@ +error: code is excluded from test builds + --> tests/ui/cfg_not_test.rs:8:5 + | +LL | #[cfg(not(test))] + | ^^^^^^^^^^^^^^^^^ + | + = help: consider not excluding any code from test builds + = note: this could increase code coverage despite not actually being tested + = note: `-D clippy::cfg-not-test` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::cfg_not_test)]` + +error: code is excluded from test builds + --> tests/ui/cfg_not_test.rs:12:5 + | +LL | #[cfg(not(test))] + | ^^^^^^^^^^^^^^^^^ + | + = help: consider not excluding any code from test builds + +error: code is excluded from test builds + --> tests/ui/cfg_not_test.rs:24:1 + | +LL | #[cfg(not(test))] + | ^^^^^^^^^^^^^^^^^ + | + = help: consider not excluding any code from test builds + +error: code is excluded from test builds + --> tests/ui/cfg_not_test.rs:26:1 + | +LL | #[cfg(all(debug_assertions, not(test)))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider not excluding any code from test builds + +error: code is excluded from test builds + --> tests/ui/cfg_not_test.rs:28:1 + | +LL | #[cfg(not(any(not(debug_assertions), test)))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider not excluding any code from test builds + +error: aborting due to 5 previous errors + From 6621e6cbfaddd39a7abeab92a30c7c6175184a6f Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 21 Jun 2024 17:53:22 +0500 Subject: [PATCH 055/107] Rename thread_local_initializer_can_be_made_const to missing_const_for_thread_local --- CHANGELOG.md | 1 + clippy_config/src/msrvs.rs | 2 +- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 7 +- ...t.rs => missing_const_for_thread_local.rs} | 14 +- clippy_lints/src/renamed_lints.rs | 1 + ...d => missing_const_for_thread_local.fixed} | 2 +- ...t.rs => missing_const_for_thread_local.rs} | 2 +- ... => missing_const_for_thread_local.stderr} | 16 +-- tests/ui/rename.fixed | 2 + tests/ui/rename.rs | 2 + tests/ui/rename.stderr | 124 +++++++++--------- 12 files changed, 93 insertions(+), 82 deletions(-) rename clippy_lints/src/{thread_local_initializer_can_be_made_const.rs => missing_const_for_thread_local.rs} (92%) rename tests/ui/{thread_local_initializer_can_be_made_const.fixed => missing_const_for_thread_local.fixed} (97%) rename tests/ui/{thread_local_initializer_can_be_made_const.rs => missing_const_for_thread_local.rs} (97%) rename tests/ui/{thread_local_initializer_can_be_made_const.stderr => missing_const_for_thread_local.stderr} (72%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d919b521e7155..8e5a7a34c8cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5589,6 +5589,7 @@ Released 2018-09-13 [`missing_assert_message`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_assert_message [`missing_asserts_for_indexing`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_asserts_for_indexing [`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn +[`missing_const_for_thread_local`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_thread_local [`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items [`missing_enforced_import_renames`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_enforced_import_renames [`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 78fd6149dc098..fc56ac5179686 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -26,7 +26,7 @@ msrv_aliases! { 1,65,0 { LET_ELSE, POINTER_CAST_CONSTNESS } 1,63,0 { CLONE_INTO } 1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE, CONST_EXTERN_FN } - 1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST } + 1,59,0 { THREAD_LOCAL_CONST_INIT } 1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF } 1,56,0 { CONST_FN_UNION } 1,55,0 { SEEK_REWIND } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 50661bf46a0a3..8b1b16de0a12d 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -505,6 +505,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO, crate::missing_asserts_for_indexing::MISSING_ASSERTS_FOR_INDEXING_INFO, crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO, + crate::missing_const_for_thread_local::MISSING_CONST_FOR_THREAD_LOCAL_INFO, crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO, crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO, crate::missing_fields_in_debug::MISSING_FIELDS_IN_DEBUG_INFO, @@ -682,7 +683,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO, crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO, crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO, - crate::thread_local_initializer_can_be_made_const::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST_INFO, crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO, crate::to_string_trait_impl::TO_STRING_TRAIT_IMPL_INFO, crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8a9d3b43fb3ab..c09d23d06ffba 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -230,6 +230,7 @@ mod mismatching_type_param_order; mod missing_assert_message; mod missing_asserts_for_indexing; mod missing_const_for_fn; +mod missing_const_for_thread_local; mod missing_doc; mod missing_enforced_import_rename; mod missing_fields_in_debug; @@ -341,7 +342,6 @@ mod swap_ptr_to_ref; mod tabs_in_doc_comments; mod temporary_assignment; mod tests_outside_test_module; -mod thread_local_initializer_can_be_made_const; mod to_digit_is_some; mod to_string_trait_impl; mod trailing_empty_array; @@ -1156,9 +1156,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { behavior: pub_underscore_fields_behavior, }) }); - store.register_late_pass(move |_| { - Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv())) - }); + store + .register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(msrv()))); store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv()))); store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); diff --git a/clippy_lints/src/thread_local_initializer_can_be_made_const.rs b/clippy_lints/src/missing_const_for_thread_local.rs similarity index 92% rename from clippy_lints/src/thread_local_initializer_can_be_made_const.rs rename to clippy_lints/src/missing_const_for_thread_local.rs index 4af3ee74d0eaf..ab1b4aa3dee60 100644 --- a/clippy_lints/src/thread_local_initializer_can_be_made_const.rs +++ b/clippy_lints/src/missing_const_for_thread_local.rs @@ -39,23 +39,23 @@ declare_clippy_lint! { /// } /// ``` #[clippy::version = "1.77.0"] - pub THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, + pub MISSING_CONST_FOR_THREAD_LOCAL, perf, "suggest using `const` in `thread_local!` macro" } -pub struct ThreadLocalInitializerCanBeMadeConst { +pub struct MissingConstForThreadLocal { msrv: Msrv, } -impl ThreadLocalInitializerCanBeMadeConst { +impl MissingConstForThreadLocal { #[must_use] pub fn new(msrv: Msrv) -> Self { Self { msrv } } } -impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]); +impl_lint_pass!(MissingConstForThreadLocal => [MISSING_CONST_FOR_THREAD_LOCAL]); #[inline] fn is_thread_local_initializer( @@ -102,7 +102,7 @@ fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id false } -impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { +impl<'tcx> LateLintPass<'tcx> for MissingConstForThreadLocal { fn check_fn( &mut self, cx: &LateContext<'tcx>, @@ -113,7 +113,7 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { local_defid: rustc_span::def_id::LocalDefId, ) { let defid = local_defid.to_def_id(); - if self.msrv.meets(msrvs::THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST) + if self.msrv.meets(msrvs::THREAD_LOCAL_CONST_INIT) && is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false) // Some implementations of `thread_local!` include an initializer fn. // In the case of a const initializer, the init fn is also const, @@ -139,7 +139,7 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst { { span_lint_and_sugg( cx, - THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST, + MISSING_CONST_FOR_THREAD_LOCAL, unpeeled.span, "initializer for `thread_local` value can be made `const`", "replace with", diff --git a/clippy_lints/src/renamed_lints.rs b/clippy_lints/src/renamed_lints.rs index 85979903b587a..e0e1279873e69 100644 --- a/clippy_lints/src/renamed_lints.rs +++ b/clippy_lints/src/renamed_lints.rs @@ -32,6 +32,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::result_unwrap_used", "clippy::unwrap_used"), ("clippy::single_char_push_str", "clippy::single_char_add_str"), ("clippy::stutter", "clippy::module_name_repetitions"), + ("clippy::thread_local_initializer_can_be_made_const", "clippy::missing_const_for_thread_local"), ("clippy::to_string_in_display", "clippy::recursive_format_impl"), ("clippy::unwrap_or_else_default", "clippy::unwrap_or_default"), ("clippy::zero_width_space", "clippy::invisible_characters"), diff --git a/tests/ui/thread_local_initializer_can_be_made_const.fixed b/tests/ui/missing_const_for_thread_local.fixed similarity index 97% rename from tests/ui/thread_local_initializer_can_be_made_const.fixed rename to tests/ui/missing_const_for_thread_local.fixed index 4c9bd0bd8634f..90b31b9b5d219 100644 --- a/tests/ui/thread_local_initializer_can_be_made_const.fixed +++ b/tests/ui/missing_const_for_thread_local.fixed @@ -1,4 +1,4 @@ -#![warn(clippy::thread_local_initializer_can_be_made_const)] +#![warn(clippy::missing_const_for_thread_local)] use std::cell::{Cell, RefCell}; diff --git a/tests/ui/thread_local_initializer_can_be_made_const.rs b/tests/ui/missing_const_for_thread_local.rs similarity index 97% rename from tests/ui/thread_local_initializer_can_be_made_const.rs rename to tests/ui/missing_const_for_thread_local.rs index eb336f0dd1913..f97e4848fd7a6 100644 --- a/tests/ui/thread_local_initializer_can_be_made_const.rs +++ b/tests/ui/missing_const_for_thread_local.rs @@ -1,4 +1,4 @@ -#![warn(clippy::thread_local_initializer_can_be_made_const)] +#![warn(clippy::missing_const_for_thread_local)] use std::cell::{Cell, RefCell}; diff --git a/tests/ui/thread_local_initializer_can_be_made_const.stderr b/tests/ui/missing_const_for_thread_local.stderr similarity index 72% rename from tests/ui/thread_local_initializer_can_be_made_const.stderr rename to tests/ui/missing_const_for_thread_local.stderr index b4f8bd822b0de..c143a37454f5b 100644 --- a/tests/ui/thread_local_initializer_can_be_made_const.stderr +++ b/tests/ui/missing_const_for_thread_local.stderr @@ -1,38 +1,38 @@ error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:8:41 + --> tests/ui/missing_const_for_thread_local.rs:8:41 | LL | static BUF_1: RefCell = RefCell::new(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` | - = note: `-D clippy::thread-local-initializer-can-be-made-const` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::thread_local_initializer_can_be_made_const)]` + = note: `-D clippy::missing-const-for-thread-local` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_const_for_thread_local)]` error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:18:29 + --> tests/ui/missing_const_for_thread_local.rs:18:29 | LL | static SIMPLE:i32 = 1; | ^ help: replace with: `const { 1 }` error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:24:59 + --> tests/ui/missing_const_for_thread_local.rs:24:59 | LL | static BUF_3_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:26:59 + --> tests/ui/missing_const_for_thread_local.rs:26:59 | LL | static BUF_4_CAN_BE_MADE_CONST: RefCell = RefCell::new(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }` error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:32:31 + --> tests/ui/missing_const_for_thread_local.rs:32:31 | LL | static PEEL_ME: i32 = { 1 }; | ^^^^^ help: replace with: `const { 1 }` error: initializer for `thread_local` value can be made `const` - --> tests/ui/thread_local_initializer_can_be_made_const.rs:34:36 + --> tests/ui/missing_const_for_thread_local.rs:34:36 | LL | static PEEL_ME_MANY: i32 = { let x = 1; x * x }; | ^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { { let x = 1; x * x } }` diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index 24d0f79754289..df39afcc1ad66 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -27,6 +27,7 @@ #![allow(clippy::needless_borrow)] #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] +#![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] @@ -83,6 +84,7 @@ #![warn(clippy::unwrap_used)] #![warn(clippy::single_char_add_str)] #![warn(clippy::module_name_repetitions)] +#![warn(clippy::missing_const_for_thread_local)] #![warn(clippy::recursive_format_impl)] #![warn(clippy::unwrap_or_default)] #![warn(clippy::invisible_characters)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index be8da2fa1a38e..c57a4e1bde267 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -27,6 +27,7 @@ #![allow(clippy::needless_borrow)] #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] +#![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::recursive_format_impl)] #![allow(clippy::unwrap_or_default)] #![allow(clippy::invisible_characters)] @@ -83,6 +84,7 @@ #![warn(clippy::result_unwrap_used)] #![warn(clippy::single_char_push_str)] #![warn(clippy::stutter)] +#![warn(clippy::thread_local_initializer_can_be_made_const)] #![warn(clippy::to_string_in_display)] #![warn(clippy::unwrap_or_else_default)] #![warn(clippy::zero_width_space)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 777ac20153da3..3abab593a6ba0 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:56:9 + --> tests/ui/rename.rs:57:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,346 +8,352 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:57:9 + --> tests/ui/rename.rs:58:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:58:9 + --> tests/ui/rename.rs:59:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:59:9 + --> tests/ui/rename.rs:60:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:60:9 + --> tests/ui/rename.rs:61:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:61:9 + --> tests/ui/rename.rs:62:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:62:9 + --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:63:9 + --> tests/ui/rename.rs:64:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:64:9 + --> tests/ui/rename.rs:65:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:65:9 + --> tests/ui/rename.rs:66:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:66:9 + --> tests/ui/rename.rs:67:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:68:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:69:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:79:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:80:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:81:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:82:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:83:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:84:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:85:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:86:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` +error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` + --> tests/ui/rename.rs:87:9 + | +LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` + error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:88:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:89:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:90:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:91:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:92:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:93:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:94:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:95:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:96:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:97:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:98:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:99:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:100:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:101:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:102:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:103:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:104:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:105:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:106:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:107:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:108:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:109:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:110:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:111:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:112:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:113:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:114:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:115:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` -error: aborting due to 58 previous errors +error: aborting due to 59 previous errors From 25bb612538e7883fd30c355fbccf9457a46fec01 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 5 Jul 2024 16:36:24 +0200 Subject: [PATCH 056/107] Lintcheck: Add `--warn-all` and make it the CI default --- .github/workflows/lintcheck.yml | 4 +-- lintcheck/src/config.rs | 4 +++ lintcheck/src/main.rs | 56 ++++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 91c98b3a2560c..f016a77005920 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -58,7 +58,7 @@ jobs: - name: Run lintcheck if: steps.cache-json.outputs.cache-hit != 'true' - run: ./target/debug/lintcheck --format json + run: ./target/debug/lintcheck --format json --warn-all - name: Upload base JSON uses: actions/upload-artifact@v4 @@ -86,7 +86,7 @@ jobs: run: cargo build --manifest-path=lintcheck/Cargo.toml - name: Run lintcheck - run: ./target/debug/lintcheck --format json + run: ./target/debug/lintcheck --format json --warn-all - name: Upload head JSON uses: actions/upload-artifact@v4 diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index e6cd7c9fdc2bf..b35a62eed4405 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -36,6 +36,10 @@ pub(crate) struct LintcheckConfig { /// Apply a filter to only collect specified lints, this also overrides `allow` attributes #[clap(long = "filter", value_name = "clippy_lint_name", use_value_delimiter = true)] pub lint_filter: Vec, + /// Set all lints to the "warn" lint level, even resitriction ones. Usually, + /// it's better to use `--filter` instead + #[clap(long, conflicts_with("lint_filter"))] + pub warn_all: bool, /// Set the output format of the log file #[clap(long, short, default_value = "text")] pub format: OutputFormat, diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index ec72e0eb5dcb5..70b8af0fdb95c 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -5,6 +5,7 @@ // When a new lint is introduced, we can search the results for new warnings and check for false // positives. +#![feature(iter_collect_into)] #![warn( trivial_casts, trivial_numeric_casts, @@ -352,7 +353,7 @@ impl Crate { target_dir_index: &AtomicUsize, total_crates_to_lint: usize, config: &LintcheckConfig, - lint_filter: &[String], + lint_levels_args: &[String], server: &Option, ) -> Vec { // advance the atomic index by one @@ -398,16 +399,9 @@ impl Crate { for opt in options { clippy_args.push(opt); } - } else { - clippy_args.extend(["-Wclippy::pedantic", "-Wclippy::cargo"]); } - if lint_filter.is_empty() { - clippy_args.push("--cap-lints=warn"); - } else { - clippy_args.push("--cap-lints=allow"); - clippy_args.extend(lint_filter.iter().map(String::as_str)); - } + clippy_args.extend(lint_levels_args.iter().map(String::as_str)); let mut cmd = Command::new("cargo"); cmd.arg(if config.fix { "fix" } else { "check" }) @@ -638,15 +632,39 @@ fn lintcheck(config: LintcheckConfig) { let (crates, recursive_options) = read_crates(&config.sources_toml_path); let counter = AtomicUsize::new(1); - let lint_filter: Vec = config - .lint_filter - .iter() - .map(|filter| { - let mut filter = filter.clone(); - filter.insert_str(0, "--force-warn="); - filter - }) - .collect(); + let mut lint_level_args: Vec = vec![]; + if config.lint_filter.is_empty() { + lint_level_args.push("--cap-lints=warn".to_string()); + + // Set allow-by-default to warn + if config.warn_all { + [ + "clippy::cargo", + "clippy::nursery", + "clippy::pedantic", + "clippy::restriction", + ] + .iter() + .map(|group| format!("--warn={group}")) + .collect_into(&mut lint_level_args); + } else { + ["clippy::cargo", "clippy::pedantic"] + .iter() + .map(|group| format!("--warn={group}")) + .collect_into(&mut lint_level_args); + } + } else { + lint_level_args.push("--cap-lints=allow".to_string()); + config + .lint_filter + .iter() + .map(|filter| { + let mut filter = filter.clone(); + filter.insert_str(0, "--force-warn="); + filter + }) + .collect_into(&mut lint_level_args); + }; let crates: Vec = crates .into_iter() @@ -698,7 +716,7 @@ fn lintcheck(config: LintcheckConfig) { &counter, crates.len(), &config, - &lint_filter, + &lint_level_args, &server, ) }) From ecbb2d7ba95b94a25df55a01669d8cda080b7011 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Sat, 6 Jul 2024 00:51:58 +0200 Subject: [PATCH 057/107] remove internal `compiler_lint_functions` lint --- clippy.toml | 4 + clippy_lints/src/declared_lints.rs | 2 - clippy_lints/src/lib.rs | 3 - clippy_lints/src/utils/internal_lints.rs | 1 - .../internal_lints/compiler_lint_functions.rs | 73 ------------------- 5 files changed, 4 insertions(+), 79 deletions(-) delete mode 100644 clippy_lints/src/utils/internal_lints/compiler_lint_functions.rs diff --git a/clippy.toml b/clippy.toml index 62ed55beb1f34..319b72e8c5d50 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,5 +1,9 @@ avoid-breaking-exported-api = false +[[disallowed-methods]] +path = "rustc_lint::context::LintContext::lint" +reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" + [[disallowed-methods]] path = "rustc_lint::context::LintContext::span_lint" reason = "this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead" diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 50661bf46a0a3..8ba5fa6a56081 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -8,8 +8,6 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS_INFO, #[cfg(feature = "internal")] - crate::utils::internal_lints::compiler_lint_functions::COMPILER_LINT_FUNCTIONS_INFO, - #[cfg(feature = "internal")] crate::utils::internal_lints::interning_defined_symbol::INTERNING_DEFINED_SYMBOL_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::interning_defined_symbol::UNNECESSARY_SYMBOL_STR_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8a9d3b43fb3ab..5de31d50156e7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -641,9 +641,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { }); store.register_early_pass(|| Box::new(utils::internal_lints::produce_ice::ProduceIce)); store.register_late_pass(|_| Box::new(utils::internal_lints::collapsible_calls::CollapsibleCalls)); - store.register_late_pass(|_| { - Box::new(utils::internal_lints::compiler_lint_functions::CompilerLintFunctions::new()) - }); store.register_late_pass(|_| Box::new(utils::internal_lints::invalid_paths::InvalidPaths)); store.register_late_pass(|_| { Box::::default() diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 877a77fd6d242..1d294c2944b2c 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -1,6 +1,5 @@ pub mod almost_standard_lint_formulation; pub mod collapsible_calls; -pub mod compiler_lint_functions; pub mod interning_defined_symbol; pub mod invalid_paths; pub mod lint_without_lint_pass; diff --git a/clippy_lints/src/utils/internal_lints/compiler_lint_functions.rs b/clippy_lints/src/utils/internal_lints/compiler_lint_functions.rs deleted file mode 100644 index 9b6b687181867..0000000000000 --- a/clippy_lints/src/utils/internal_lints/compiler_lint_functions.rs +++ /dev/null @@ -1,73 +0,0 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::ty::match_type; -use clippy_utils::{is_lint_allowed, paths}; -use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Expr, ExprKind}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::impl_lint_pass; - -declare_clippy_lint! { - /// ### What it does - /// Checks for calls to `cx.span_lint*` and suggests to use the `utils::*` - /// variant of the function. - /// - /// ### Why is this bad? - /// The `utils::*` variants also add a link to the Clippy documentation to the - /// warning/error messages. - /// - /// ### Example - /// ```rust,ignore - /// cx.span_lint(LINT_NAME, "message"); - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// utils::span_lint(cx, LINT_NAME, "message"); - /// ``` - pub COMPILER_LINT_FUNCTIONS, - internal, - "usage of the lint functions of the compiler instead of the utils::* variant" -} - -impl_lint_pass!(CompilerLintFunctions => [COMPILER_LINT_FUNCTIONS]); - -#[derive(Clone, Default)] -pub struct CompilerLintFunctions { - map: FxHashMap<&'static str, &'static str>, -} - -impl CompilerLintFunctions { - #[must_use] - pub fn new() -> Self { - let mut map = FxHashMap::default(); - map.insert("span_lint", "utils::span_lint"); - map.insert("lint", "utils::span_lint"); - map.insert("span_lint_note", "utils::span_lint_and_note"); - map.insert("span_lint_help", "utils::span_lint_and_help"); - Self { map } - } -} - -impl<'tcx> LateLintPass<'tcx> for CompilerLintFunctions { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if is_lint_allowed(cx, COMPILER_LINT_FUNCTIONS, expr.hir_id) { - return; - } - - if let ExprKind::MethodCall(path, self_arg, _, _) = &expr.kind - && let fn_name = path.ident - && let Some(sugg) = self.map.get(fn_name.as_str()) - && let ty = cx.typeck_results().expr_ty(self_arg).peel_refs() - && (match_type(cx, ty, &paths::EARLY_CONTEXT) || match_type(cx, ty, &paths::LATE_CONTEXT)) - { - span_lint_and_help( - cx, - COMPILER_LINT_FUNCTIONS, - path.ident.span, - "usage of a compiler lint function", - None, - format!("please use the Clippy variant of this function: `{sugg}`"), - ); - } - } -} From d8fb164a7cfde2cdd70d2070b6e01d70a65217aa Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 9 Jun 2024 15:46:39 -0400 Subject: [PATCH 058/107] Rename `any_parent_is_automatically_derived` to `in_automatically_derived`. Only check for the attribute on an impl block. --- clippy_lints/src/default.rs | 10 +++++----- clippy_lints/src/misc.rs | 6 +++--- clippy_lints/src/no_effect.rs | 4 ++-- clippy_utils/src/lib.rs | 19 +++++++++++++++---- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 2b3f4854255cf..72fa05be3cc60 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_drop, is_copy}; -use clippy_utils::{any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro}; +use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -84,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { // Avoid cases already linted by `field_reassign_with_default` && !self.reassigned_linted.contains(&expr.span) && let ExprKind::Call(path, ..) = expr.kind - && !any_parent_is_automatically_derived(cx.tcx, expr.hir_id) + && !in_automatically_derived(cx.tcx, expr.hir_id) && let ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::default_fn, def_id) @@ -113,9 +113,9 @@ impl<'tcx> LateLintPass<'tcx> for Default { // start from the `let mut _ = _::default();` and look at all the following // statements, see if they re-assign the fields of the binding let stmts_head = match block.stmts { + [] | [_] => return, // Skip the last statement since there cannot possibly be any following statements that re-assign fields. - [head @ .., _] if !head.is_empty() => head, - _ => return, + [head @ .., _] => head, }; for (stmt_idx, stmt) in stmts_head.iter().enumerate() { // find all binding statements like `let mut _ = T::default()` where `T::default()` is the @@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { let (local, variant, binding_name, binding_type, span) = if let StmtKind::Let(local) = stmt.kind // only take `let ...` statements && let Some(expr) = local.init - && !any_parent_is_automatically_derived(cx.tcx, expr.hir_id) + && !in_automatically_derived(cx.tcx, expr.hir_id) && !expr.span.from_expansion() // only take bindings to identifiers && let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index f3f9bf11a61c4..32c8731c53743 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -2,8 +2,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_then, span_lint_hir_and use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - any_parent_is_automatically_derived, fulfill_or_allowed, get_parent_expr, is_lint_allowed, iter_input_pats, - last_path_segment, SpanlessEq, + fulfill_or_allowed, get_parent_expr, in_automatically_derived, is_lint_allowed, iter_input_pats, last_path_segment, + SpanlessEq, }; use rustc_errors::Applicability; use rustc_hir::def::Res; @@ -206,7 +206,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if in_external_macro(cx.sess(), expr.span) || expr.span.desugaring_kind().is_some() - || any_parent_is_automatically_derived(cx.tcx, expr.hir_id) + || in_automatically_derived(cx.tcx, expr.hir_id) { return; } diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 139c33d3f4a42..0ecfa7baa72d4 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; use clippy_utils::source::snippet_opt; use clippy_utils::ty::has_drop; use clippy_utils::{ - any_parent_is_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks, + in_automatically_derived, is_inside_always_const_context, is_lint_allowed, path_to_local, peel_blocks, }; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -187,7 +187,7 @@ impl NoEffect { && has_no_effect(cx, init) && let PatKind::Binding(_, hir_id, ident, _) = local.pat.kind && ident.name.to_ident_string().starts_with('_') - && !any_parent_is_automatically_derived(cx.tcx, local.hir_id) + && !in_automatically_derived(cx.tcx, local.hir_id) { if let Some(l) = self.local_bindings.last_mut() { l.push(hir_id); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 28d1eeb4e28c5..c24cb12845baf 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -103,8 +103,9 @@ use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::{ self as hir, def, Arm, ArrayLen, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstContext, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, - ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, Param, Pat, PatKind, Path, - PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, + ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat, + PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitItemRef, TraitRef, + TyKind, UnOp, }; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::{LateContext, Level, Lint, LintContext}; @@ -1924,8 +1925,18 @@ pub fn any_parent_has_attr(tcx: TyCtxt<'_>, node: HirId, symbol: Symbol) -> bool false } -pub fn any_parent_is_automatically_derived(tcx: TyCtxt<'_>, node: HirId) -> bool { - any_parent_has_attr(tcx, node, sym::automatically_derived) +/// Checks if the given HIR node is inside an `impl` block with the `automatically_derived` +/// attribute. +pub fn in_automatically_derived(tcx: TyCtxt<'_>, id: HirId) -> bool { + tcx.hir() + .parent_owner_iter(id) + .filter(|(_, node)| matches!(node, OwnerNode::Item(item) if matches!(item.kind, ItemKind::Impl(_)))) + .any(|(id, _)| { + has_attr( + tcx.hir().attrs(tcx.local_def_id_to_hir_id(id.def_id)), + sym::automatically_derived, + ) + }) } /// Matches a function call with the given path and returns the arguments. From e0f32e09de8697ffb3fde577ce8730da6cb1367a Mon Sep 17 00:00:00 2001 From: lukas Date: Thu, 4 Jul 2024 16:47:20 +0200 Subject: [PATCH 059/107] Mark format! with must_use hint --- tests/ui/or_fun_call.fixed | 2 +- tests/ui/or_fun_call.stderr | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 7657ef470c589..c76f7a81843b2 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -98,7 +98,7 @@ fn or_fun_call() { let opt = Some(1); let hello = "Hello"; - let _ = opt.ok_or(format!("{} world.", hello)); + let _ = opt.ok_or_else(|| format!("{} world.", hello)); // index let map = HashMap::::new(); diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index b5a30f2992316..3070db22fc5a6 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -100,6 +100,12 @@ error: use of `unwrap_or` to construct default value LL | let _ = stringy.unwrap_or(String::new()); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` +error: use of `ok_or` followed by a function call + --> tests/ui/or_fun_call.rs:101:17 + | +LL | let _ = opt.ok_or(format!("{} world.", hello)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `ok_or_else(|| format!("{} world.", hello))` + error: use of `unwrap_or` followed by a function call --> tests/ui/or_fun_call.rs:105:21 | @@ -190,5 +196,5 @@ error: use of `unwrap_or_else` to construct default value LL | let _ = stringy.unwrap_or_else(String::new); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `unwrap_or_default()` -error: aborting due to 31 previous errors +error: aborting due to 32 previous errors From 7ce4a499119c2a816a40af73b6b4cc39b5278400 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 11:54:22 -0400 Subject: [PATCH 060/107] iter_identity is a better name --- clippy_utils/src/ty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e5d2056419681..acaeb93f44a6e 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -99,7 +99,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' for (predicate, _span) in cx .tcx .explicit_item_super_predicates(def_id) - .instantiate_identity_iter_copied() + .iter_identity_copied() { match predicate.kind().skip_binder() { // For `impl Trait`, it will register a predicate of `T: Trait`, so we go through From f7050b0c784559a0eacbbfac9f7be95cb81dba66 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Sun, 7 Jul 2024 10:44:27 +0100 Subject: [PATCH 061/107] resolve code review comments --- clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/casts/mod.rs | 12 ++--- clippy_lints/src/casts/ptr_as_ptr.rs | 2 +- clippy_lints/src/methods/mod.rs | 3 +- tests/ui/crashes/ice-12616.stderr | 2 +- tests/ui/ptr_as_ptr.stderr | 66 +++++++++++------------ 6 files changed, 43 insertions(+), 44 deletions(-) diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index dac1f4ed404a1..1a4dc38e7e9c9 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// `MutexGuard`. /// /// ### Why is this bad? - /// The Mutex types found in [`std::sync`] and + /// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are /// not designed to operate in an async context across await points. /// diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index d8bae3b48bf4f..7eaa802ecc28e 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -244,9 +244,9 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// Casting a function pointer to anything other than `usize`/`isize` is - /// not portable across architectures. It either loses bits if the target - /// type is too small, or creates extra bits that waste space and bloat the - /// resulting binary. + /// not portable across architectures. If the target type is too small the + /// address would be truncated, and target types larger than `usize` are + /// unnecessary. /// /// Casting to `isize` also doesn't make sense, since addresses are never /// signed. @@ -373,7 +373,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for `as` casts on a raw pointer that don't change its + /// Checks for `as` casts between raw pointers that don't change their /// mutability, namely `*const T` to `*const U` and `*mut T` to `*mut U`. /// /// ### Why is this bad? @@ -398,12 +398,12 @@ declare_clippy_lint! { #[clippy::version = "1.51.0"] pub PTR_AS_PTR, pedantic, - "casting using `as` on a raw pointer that doesn't change its mutability, where `pointer::cast` could take the place of `as`" + "casting using `as` between raw pointers that doesn't change their constness, where `pointer::cast` could take the place of `as`" } declare_clippy_lint! { /// ### What it does - /// Checks for `as` casts on a raw pointer that change its constness, namely `*const T` to + /// Checks for `as` casts between raw pointers that change their constness, namely `*const T` to /// `*mut T` and `*mut T` to `*const T`. /// /// ### Why is this bad? diff --git a/clippy_lints/src/casts/ptr_as_ptr.rs b/clippy_lints/src/casts/ptr_as_ptr.rs index 2c168405ee260..86c5f6b9f0ba0 100644 --- a/clippy_lints/src/casts/ptr_as_ptr.rs +++ b/clippy_lints/src/casts/ptr_as_ptr.rs @@ -92,7 +92,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, msrv: &Msrv) { cx, PTR_AS_PTR, expr.span, - "`as` casting between raw pointers without changing its mutability", + "`as` casting between raw pointers without changing their constness", help, final_suggestion, app, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index b8b24fe92ee33..c29f81bf0fb4b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -632,8 +632,7 @@ declare_clippy_lint! { /// or `_.or_else(|x| Err(y))`. /// /// ### Why is this bad? - /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| - /// y)`. + /// This can be written more concisely as `_.map(|x| y)` or `_.map_err(|x| y)`. /// /// ### Example /// ```no_run diff --git a/tests/ui/crashes/ice-12616.stderr b/tests/ui/crashes/ice-12616.stderr index c7cf5cf5483f7..a84a945a429ff 100644 --- a/tests/ui/crashes/ice-12616.stderr +++ b/tests/ui/crashes/ice-12616.stderr @@ -1,4 +1,4 @@ -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/crashes/ice-12616.rs:6:5 | LL | s() as *const (); diff --git a/tests/ui/ptr_as_ptr.stderr b/tests/ui/ptr_as_ptr.stderr index e162f35baf551..18462620b0a30 100644 --- a/tests/ui/ptr_as_ptr.stderr +++ b/tests/ui/ptr_as_ptr.stderr @@ -1,4 +1,4 @@ -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:18:33 | LL | *unsafe { Box::from_raw(Box::into_raw(Box::new(o)) as *mut super::issue_11278_a::T) } @@ -7,37 +7,37 @@ LL | *unsafe { Box::from_raw(Box::into_raw(Box::new(o)) as *mut super::i = note: `-D clippy::ptr-as-ptr` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ptr_as_ptr)]` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:27:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:28:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:33:17 | LL | let _ = *ptr_ptr as *const i32; | ^^^^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `(*ptr_ptr).cast::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:46:25 | LL | let _: *const i32 = ptr as *const _; | ^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:47:23 | LL | let _: *mut i32 = mut_ptr as _; | ^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:50:21 | LL | let _ = inline!($ptr as *const i32); @@ -45,157 +45,157 @@ LL | let _ = inline!($ptr as *const i32); | = note: this error originates in the macro `__inline_mac_fn_main` (in Nightly builds, run with -Z macro-backtrace for more info) -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:71:13 | LL | let _ = ptr as *const i32; | ^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `ptr.cast::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:72:13 | LL | let _ = mut_ptr as *mut i32; | ^^^^^^^^^^^^^^^^^^^ help: try `pointer::cast`, a safer alternative: `mut_ptr.cast::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:79:9 | LL | ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:83:9 | LL | std::ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:88:9 | LL | ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:92:9 | LL | core::ptr::null_mut() as *mut u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:97:9 | LL | ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:101:9 | LL | std::ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:106:9 | LL | ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:110:9 | LL | core::ptr::null() as *const u32 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null::()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:117:9 | LL | ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:121:9 | LL | std::ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:126:9 | LL | ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:130:9 | LL | core::ptr::null_mut() as *mut _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:135:9 | LL | ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:139:9 | LL | std::ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:144:9 | LL | ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:148:9 | LL | core::ptr::null() as *const _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:155:9 | LL | ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:159:9 | LL | std::ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:164:9 | LL | ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:168:9 | LL | core::ptr::null_mut() as _ | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `core::ptr::null_mut()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:173:9 | LL | ptr::null() as _ | ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:177:9 | LL | std::ptr::null() as _ | ^^^^^^^^^^^^^^^^^^^^^ help: try call directly: `std::ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:182:9 | LL | ptr::null() as _ | ^^^^^^^^^^^^^^^^ help: try call directly: `ptr::null()` -error: `as` casting between raw pointers without changing its mutability +error: `as` casting between raw pointers without changing their constness --> tests/ui/ptr_as_ptr.rs:186:9 | LL | core::ptr::null() as _ From 5a9e5e4acf537bca80dc505c48148c952b85a264 Mon Sep 17 00:00:00 2001 From: John Arundel Date: Sun, 7 Jul 2024 10:49:26 +0100 Subject: [PATCH 062/107] resolve code review comments --- clippy_lints/src/casts/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 7eaa802ecc28e..14f9f991d3be2 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -374,7 +374,7 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Checks for `as` casts between raw pointers that don't change their - /// mutability, namely `*const T` to `*const U` and `*mut T` to `*mut U`. + /// constness, namely `*const T` to `*const U` and `*mut T` to `*mut U`. /// /// ### Why is this bad? /// Though `as` casts between raw pointers are not terrible, From aa371c37c2989e2c505865630179250ec528c19b Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 16 Jun 2024 01:34:25 -0400 Subject: [PATCH 063/107] Rewrite `overflow_check_conditional` --- .../src/overflow_check_conditional.rs | 66 +++++++++---------- tests/ui/overflow_check_conditional.rs | 21 ++---- tests/ui/overflow_check_conditional.stderr | 28 +++----- 3 files changed, 45 insertions(+), 70 deletions(-) diff --git a/clippy_lints/src/overflow_check_conditional.rs b/clippy_lints/src/overflow_check_conditional.rs index de78987933108..6909c1f6f671e 100644 --- a/clippy_lints/src/overflow_check_conditional.rs +++ b/clippy_lints/src/overflow_check_conditional.rs @@ -1,7 +1,9 @@ use clippy_utils::diagnostics::span_lint; -use clippy_utils::SpanlessEq; -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use clippy_utils::eq_expr_value; +use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; use rustc_session::declare_lint_pass; declare_clippy_lint! { @@ -26,45 +28,39 @@ declare_clippy_lint! { declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]); -const OVERFLOW_MSG: &str = "you are trying to use classic C overflow conditions that will fail in Rust"; -const UNDERFLOW_MSG: &str = "you are trying to use classic C underflow conditions that will fail in Rust"; - impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional { // a + b < a, a > a + b, a < a - b, a - b > a fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let eq = |l, r| SpanlessEq::new(cx).eq_path_segment(l, r); - if let ExprKind::Binary(ref op, first, second) = expr.kind - && let ExprKind::Binary(ref op2, ident1, ident2) = first.kind - && let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind - && let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind - && let ExprKind::Path(QPath::Resolved(_, path3)) = second.kind - && (eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0])) - && cx.typeck_results().expr_ty(ident1).is_integral() - && cx.typeck_results().expr_ty(ident2).is_integral() - { - if op.node == BinOpKind::Lt && op2.node == BinOpKind::Add { - span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG); + if let ExprKind::Binary(op, lhs, rhs) = expr.kind + && let (lt, gt) = match op.node { + BinOpKind::Lt => (lhs, rhs), + BinOpKind::Gt => (rhs, lhs), + _ => return, } - if op.node == BinOpKind::Gt && op2.node == BinOpKind::Sub { - span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG); + && let ctxt = expr.span.ctxt() + && let (op_lhs, op_rhs, other, commutative) = match (<.kind, >.kind) { + (&ExprKind::Binary(op, lhs, rhs), _) if op.node == BinOpKind::Add && ctxt == lt.span.ctxt() => { + (lhs, rhs, gt, true) + }, + (_, &ExprKind::Binary(op, lhs, rhs)) if op.node == BinOpKind::Sub && ctxt == gt.span.ctxt() => { + (lhs, rhs, lt, false) + }, + _ => return, } - } - - if let ExprKind::Binary(ref op, first, second) = expr.kind - && let ExprKind::Binary(ref op2, ident1, ident2) = second.kind - && let ExprKind::Path(QPath::Resolved(_, path1)) = ident1.kind - && let ExprKind::Path(QPath::Resolved(_, path2)) = ident2.kind - && let ExprKind::Path(QPath::Resolved(_, path3)) = first.kind - && (eq(&path1.segments[0], &path3.segments[0]) || eq(&path2.segments[0], &path3.segments[0])) - && cx.typeck_results().expr_ty(ident1).is_integral() - && cx.typeck_results().expr_ty(ident2).is_integral() + && let typeck = cx.typeck_results() + && let ty = typeck.expr_ty(op_lhs) + && matches!(ty.kind(), ty::Uint(_)) + && ty == typeck.expr_ty(op_rhs) + && ty == typeck.expr_ty(other) + && !in_external_macro(cx.tcx.sess, expr.span) + && (eq_expr_value(cx, op_lhs, other) || (commutative && eq_expr_value(cx, op_rhs, other))) { - if op.node == BinOpKind::Gt && op2.node == BinOpKind::Add { - span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, OVERFLOW_MSG); - } - if op.node == BinOpKind::Lt && op2.node == BinOpKind::Sub { - span_lint(cx, OVERFLOW_CHECK_CONDITIONAL, expr.span, UNDERFLOW_MSG); - } + span_lint( + cx, + OVERFLOW_CHECK_CONDITIONAL, + expr.span, + "you are trying to use classic C overflow conditions that will fail in Rust", + ); } } } diff --git a/tests/ui/overflow_check_conditional.rs b/tests/ui/overflow_check_conditional.rs index a70bb3bc47bfb..acabe9a37e007 100644 --- a/tests/ui/overflow_check_conditional.rs +++ b/tests/ui/overflow_check_conditional.rs @@ -2,23 +2,14 @@ #![allow(clippy::needless_if)] fn test(a: u32, b: u32, c: u32) { - if a + b < a {} - //~^ ERROR: you are trying to use classic C overflow conditions that will fail in Rust - //~| NOTE: `-D clippy::overflow-check-conditional` implied by `-D warnings` - if a > a + b {} - //~^ ERROR: you are trying to use classic C overflow conditions that will fail in Rust - if a + b < b {} - //~^ ERROR: you are trying to use classic C overflow conditions that will fail in Rust - if b > a + b {} - //~^ ERROR: you are trying to use classic C overflow conditions that will fail in Rust + if a + b < a {} //~ overflow_check_conditional + if a > a + b {} //~ overflow_check_conditional + if a + b < b {} //~ overflow_check_conditional + if b > a + b {} //~ overflow_check_conditional if a - b > b {} - //~^ ERROR: you are trying to use classic C underflow conditions that will fail in Rus if b < a - b {} - //~^ ERROR: you are trying to use classic C underflow conditions that will fail in Rus - if a - b > a {} - //~^ ERROR: you are trying to use classic C underflow conditions that will fail in Rus - if a < a - b {} - //~^ ERROR: you are trying to use classic C underflow conditions that will fail in Rus + if a - b > a {} //~ overflow_check_conditional + if a < a - b {} //~ overflow_check_conditional if a + b < c {} if c > a + b {} if a - b < c {} diff --git a/tests/ui/overflow_check_conditional.stderr b/tests/ui/overflow_check_conditional.stderr index c14532bad5ab7..e71768d3745e8 100644 --- a/tests/ui/overflow_check_conditional.stderr +++ b/tests/ui/overflow_check_conditional.stderr @@ -8,46 +8,34 @@ LL | if a + b < a {} = help: to override `-D warnings` add `#[allow(clippy::overflow_check_conditional)]` error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:8:8 + --> tests/ui/overflow_check_conditional.rs:6:8 | LL | if a > a + b {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:10:8 + --> tests/ui/overflow_check_conditional.rs:7:8 | LL | if a + b < b {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:12:8 + --> tests/ui/overflow_check_conditional.rs:8:8 | LL | if b > a + b {} | ^^^^^^^^^ -error: you are trying to use classic C underflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:14:8 - | -LL | if a - b > b {} - | ^^^^^^^^^ - -error: you are trying to use classic C underflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:16:8 - | -LL | if b < a - b {} - | ^^^^^^^^^ - -error: you are trying to use classic C underflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:18:8 +error: you are trying to use classic C overflow conditions that will fail in Rust + --> tests/ui/overflow_check_conditional.rs:11:8 | LL | if a - b > a {} | ^^^^^^^^^ -error: you are trying to use classic C underflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:20:8 +error: you are trying to use classic C overflow conditions that will fail in Rust + --> tests/ui/overflow_check_conditional.rs:12:8 | LL | if a < a - b {} | ^^^^^^^^^ -error: aborting due to 8 previous errors +error: aborting due to 6 previous errors From 23d96f65e42c3451dae2b70abfe0a0f709225021 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 16 Jun 2024 20:37:47 -0400 Subject: [PATCH 064/107] Rename `overflow_check_conditional` to `panicking_overflow_checks`. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 +- ...tional.rs => panicking_overflow_checks.rs} | 8 +- clippy_lints/src/renamed_lints.rs | 1 + tests/ui/overflow_check_conditional.rs | 27 ---- tests/ui/panicking_overflow_checks.rs | 27 ++++ ...tderr => panicking_overflow_checks.stderr} | 16 +-- tests/ui/rename.fixed | 2 + tests/ui/rename.rs | 2 + tests/ui/rename.stderr | 126 +++++++++--------- 11 files changed, 114 insertions(+), 102 deletions(-) rename clippy_lints/src/{overflow_check_conditional.rs => panicking_overflow_checks.rs} (91%) delete mode 100644 tests/ui/overflow_check_conditional.rs create mode 100644 tests/ui/panicking_overflow_checks.rs rename tests/ui/{overflow_check_conditional.stderr => panicking_overflow_checks.stderr} (65%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62fc5e2c5d428..55281f3cbec0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5705,6 +5705,7 @@ Released 2018-09-13 [`panic`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic [`panic_in_result_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_in_result_fn [`panic_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#panic_params +[`panicking_overflow_checks`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_overflow_checks [`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap [`partial_pub_fields`]: https://rust-lang.github.io/rust-clippy/master/index.html#partial_pub_fields [`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 593f59cff4232..0c75bcbb00b04 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -589,12 +589,12 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[ crate::operators::VERBOSE_BIT_MASK_INFO, crate::option_env_unwrap::OPTION_ENV_UNWRAP_INFO, crate::option_if_let_else::OPTION_IF_LET_ELSE_INFO, - crate::overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL_INFO, crate::panic_in_result_fn::PANIC_IN_RESULT_FN_INFO, crate::panic_unimplemented::PANIC_INFO, crate::panic_unimplemented::TODO_INFO, crate::panic_unimplemented::UNIMPLEMENTED_INFO, crate::panic_unimplemented::UNREACHABLE_INFO, + crate::panicking_overflow_checks::PANICKING_OVERFLOW_CHECKS_INFO, crate::partial_pub_fields::PARTIAL_PUB_FIELDS_INFO, crate::partialeq_ne_impl::PARTIALEQ_NE_IMPL_INFO, crate::partialeq_to_none::PARTIALEQ_TO_NONE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fbe895fa50e82..4b159417942df 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -278,9 +278,9 @@ mod only_used_in_recursion; mod operators; mod option_env_unwrap; mod option_if_let_else; -mod overflow_check_conditional; mod panic_in_result_fn; mod panic_unimplemented; +mod panicking_overflow_checks; mod partial_pub_fields; mod partialeq_ne_impl; mod partialeq_to_none; @@ -791,7 +791,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { let format_args = format_args_storage.clone(); store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); store.register_late_pass(|_| Box::new(swap::Swap)); - store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional)); + store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(disallowed_names))); store.register_late_pass(move |_| { diff --git a/clippy_lints/src/overflow_check_conditional.rs b/clippy_lints/src/panicking_overflow_checks.rs similarity index 91% rename from clippy_lints/src/overflow_check_conditional.rs rename to clippy_lints/src/panicking_overflow_checks.rs index 6909c1f6f671e..c9c2935b3ae44 100644 --- a/clippy_lints/src/overflow_check_conditional.rs +++ b/clippy_lints/src/panicking_overflow_checks.rs @@ -21,14 +21,14 @@ declare_clippy_lint! { /// a + b < a; /// ``` #[clippy::version = "pre 1.29.0"] - pub OVERFLOW_CHECK_CONDITIONAL, + pub PANICKING_OVERFLOW_CHECKS, complexity, "overflow checks inspired by C which are likely to panic" } -declare_lint_pass!(OverflowCheckConditional => [OVERFLOW_CHECK_CONDITIONAL]); +declare_lint_pass!(PanickingOverflowChecks => [PANICKING_OVERFLOW_CHECKS]); -impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional { +impl<'tcx> LateLintPass<'tcx> for PanickingOverflowChecks { // a + b < a, a > a + b, a < a - b, a - b > a fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(op, lhs, rhs) = expr.kind @@ -57,7 +57,7 @@ impl<'tcx> LateLintPass<'tcx> for OverflowCheckConditional { { span_lint( cx, - OVERFLOW_CHECK_CONDITIONAL, + PANICKING_OVERFLOW_CHECKS, expr.span, "you are trying to use classic C overflow conditions that will fail in Rust", ); diff --git a/clippy_lints/src/renamed_lints.rs b/clippy_lints/src/renamed_lints.rs index e0e1279873e69..8e999f3e89af3 100644 --- a/clippy_lints/src/renamed_lints.rs +++ b/clippy_lints/src/renamed_lints.rs @@ -26,6 +26,7 @@ pub static RENAMED_LINTS: &[(&str, &str)] = &[ ("clippy::option_map_unwrap_or", "clippy::map_unwrap_or"), ("clippy::option_map_unwrap_or_else", "clippy::map_unwrap_or"), ("clippy::option_unwrap_used", "clippy::unwrap_used"), + ("clippy::overflow_check_conditional", "clippy::panicking_overflow_checks"), ("clippy::ref_in_deref", "clippy::needless_borrow"), ("clippy::result_expect_used", "clippy::expect_used"), ("clippy::result_map_unwrap_or_else", "clippy::map_unwrap_or"), diff --git a/tests/ui/overflow_check_conditional.rs b/tests/ui/overflow_check_conditional.rs deleted file mode 100644 index acabe9a37e007..0000000000000 --- a/tests/ui/overflow_check_conditional.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![warn(clippy::overflow_check_conditional)] -#![allow(clippy::needless_if)] - -fn test(a: u32, b: u32, c: u32) { - if a + b < a {} //~ overflow_check_conditional - if a > a + b {} //~ overflow_check_conditional - if a + b < b {} //~ overflow_check_conditional - if b > a + b {} //~ overflow_check_conditional - if a - b > b {} - if b < a - b {} - if a - b > a {} //~ overflow_check_conditional - if a < a - b {} //~ overflow_check_conditional - if a + b < c {} - if c > a + b {} - if a - b < c {} - if c > a - b {} - let i = 1.1; - let j = 2.2; - if i + j < i {} - if i - j < i {} - if i > i + j {} - if i - j < i {} -} - -fn main() { - test(1, 2, 3) -} diff --git a/tests/ui/panicking_overflow_checks.rs b/tests/ui/panicking_overflow_checks.rs new file mode 100644 index 0000000000000..dc2ddeada1e94 --- /dev/null +++ b/tests/ui/panicking_overflow_checks.rs @@ -0,0 +1,27 @@ +#![warn(clippy::panicking_overflow_checks)] +#![allow(clippy::needless_if)] + +fn test(a: u32, b: u32, c: u32) { + if a + b < a {} //~ panicking_overflow_checks + if a > a + b {} //~ panicking_overflow_checks + if a + b < b {} //~ panicking_overflow_checks + if b > a + b {} //~ panicking_overflow_checks + if a - b > b {} + if b < a - b {} + if a - b > a {} //~ panicking_overflow_checks + if a < a - b {} //~ panicking_overflow_checks + if a + b < c {} + if c > a + b {} + if a - b < c {} + if c > a - b {} + let i = 1.1; + let j = 2.2; + if i + j < i {} + if i - j < i {} + if i > i + j {} + if i - j < i {} +} + +fn main() { + test(1, 2, 3) +} diff --git a/tests/ui/overflow_check_conditional.stderr b/tests/ui/panicking_overflow_checks.stderr similarity index 65% rename from tests/ui/overflow_check_conditional.stderr rename to tests/ui/panicking_overflow_checks.stderr index e71768d3745e8..1fae045788996 100644 --- a/tests/ui/overflow_check_conditional.stderr +++ b/tests/ui/panicking_overflow_checks.stderr @@ -1,38 +1,38 @@ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:5:8 + --> tests/ui/panicking_overflow_checks.rs:5:8 | LL | if a + b < a {} | ^^^^^^^^^ | - = note: `-D clippy::overflow-check-conditional` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::overflow_check_conditional)]` + = note: `-D clippy::panicking-overflow-checks` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::panicking_overflow_checks)]` error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:6:8 + --> tests/ui/panicking_overflow_checks.rs:6:8 | LL | if a > a + b {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:7:8 + --> tests/ui/panicking_overflow_checks.rs:7:8 | LL | if a + b < b {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:8:8 + --> tests/ui/panicking_overflow_checks.rs:8:8 | LL | if b > a + b {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:11:8 + --> tests/ui/panicking_overflow_checks.rs:11:8 | LL | if a - b > a {} | ^^^^^^^^^ error: you are trying to use classic C overflow conditions that will fail in Rust - --> tests/ui/overflow_check_conditional.rs:12:8 + --> tests/ui/panicking_overflow_checks.rs:12:8 | LL | if a < a - b {} | ^^^^^^^^^ diff --git a/tests/ui/rename.fixed b/tests/ui/rename.fixed index df39afcc1ad66..d70c9f8d06c07 100644 --- a/tests/ui/rename.fixed +++ b/tests/ui/rename.fixed @@ -24,6 +24,7 @@ #![allow(clippy::expect_used)] #![allow(clippy::map_unwrap_or)] #![allow(clippy::unwrap_used)] +#![allow(clippy::panicking_overflow_checks)] #![allow(clippy::needless_borrow)] #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] @@ -78,6 +79,7 @@ #![warn(clippy::map_unwrap_or)] #![warn(clippy::map_unwrap_or)] #![warn(clippy::unwrap_used)] +#![warn(clippy::panicking_overflow_checks)] #![warn(clippy::needless_borrow)] #![warn(clippy::expect_used)] #![warn(clippy::map_unwrap_or)] diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index c57a4e1bde267..8d0ac3c8f9555 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -24,6 +24,7 @@ #![allow(clippy::expect_used)] #![allow(clippy::map_unwrap_or)] #![allow(clippy::unwrap_used)] +#![allow(clippy::panicking_overflow_checks)] #![allow(clippy::needless_borrow)] #![allow(clippy::single_char_add_str)] #![allow(clippy::module_name_repetitions)] @@ -78,6 +79,7 @@ #![warn(clippy::option_map_unwrap_or)] #![warn(clippy::option_map_unwrap_or_else)] #![warn(clippy::option_unwrap_used)] +#![warn(clippy::overflow_check_conditional)] #![warn(clippy::ref_in_deref)] #![warn(clippy::result_expect_used)] #![warn(clippy::result_map_unwrap_or_else)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index 3abab593a6ba0..d6637324a030b 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,5 +1,5 @@ error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` - --> tests/ui/rename.rs:57:9 + --> tests/ui/rename.rs:58:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` @@ -8,352 +8,358 @@ LL | #![warn(clippy::almost_complete_letter_range)] = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` - --> tests/ui/rename.rs:58:9 + --> tests/ui/rename.rs:59:9 | LL | #![warn(clippy::blacklisted_name)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_names` error: lint `clippy::block_in_if_condition_expr` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:59:9 + --> tests/ui/rename.rs:60:9 | LL | #![warn(clippy::block_in_if_condition_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::block_in_if_condition_stmt` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:60:9 + --> tests/ui/rename.rs:61:9 | LL | #![warn(clippy::block_in_if_condition_stmt)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::blocks_in_if_conditions` has been renamed to `clippy::blocks_in_conditions` - --> tests/ui/rename.rs:61:9 + --> tests/ui/rename.rs:62:9 | LL | #![warn(clippy::blocks_in_if_conditions)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::blocks_in_conditions` error: lint `clippy::box_vec` has been renamed to `clippy::box_collection` - --> tests/ui/rename.rs:62:9 + --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::box_vec)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::box_collection` error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redundant_static_lifetimes` - --> tests/ui/rename.rs:63:9 + --> tests/ui/rename.rs:64:9 | LL | #![warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> tests/ui/rename.rs:64:9 + --> tests/ui/rename.rs:65:9 | LL | #![warn(clippy::cyclomatic_complexity)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` error: lint `clippy::derive_hash_xor_eq` has been renamed to `clippy::derived_hash_with_manual_eq` - --> tests/ui/rename.rs:65:9 + --> tests/ui/rename.rs:66:9 | LL | #![warn(clippy::derive_hash_xor_eq)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::derived_hash_with_manual_eq` error: lint `clippy::disallowed_method` has been renamed to `clippy::disallowed_methods` - --> tests/ui/rename.rs:66:9 + --> tests/ui/rename.rs:67:9 | LL | #![warn(clippy::disallowed_method)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_methods` error: lint `clippy::disallowed_type` has been renamed to `clippy::disallowed_types` - --> tests/ui/rename.rs:67:9 + --> tests/ui/rename.rs:68:9 | LL | #![warn(clippy::disallowed_type)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::disallowed_types` error: lint `clippy::eval_order_dependence` has been renamed to `clippy::mixed_read_write_in_expression` - --> tests/ui/rename.rs:68:9 + --> tests/ui/rename.rs:69:9 | LL | #![warn(clippy::eval_order_dependence)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::mixed_read_write_in_expression` error: lint `clippy::identity_conversion` has been renamed to `clippy::useless_conversion` - --> tests/ui/rename.rs:69:9 + --> tests/ui/rename.rs:70:9 | LL | #![warn(clippy::identity_conversion)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::useless_conversion` error: lint `clippy::if_let_some_result` has been renamed to `clippy::match_result_ok` - --> tests/ui/rename.rs:70:9 + --> tests/ui/rename.rs:71:9 | LL | #![warn(clippy::if_let_some_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::match_result_ok` error: lint `clippy::incorrect_clone_impl_on_copy_type` has been renamed to `clippy::non_canonical_clone_impl` - --> tests/ui/rename.rs:71:9 + --> tests/ui/rename.rs:72:9 | LL | #![warn(clippy::incorrect_clone_impl_on_copy_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_clone_impl` error: lint `clippy::incorrect_partial_ord_impl_on_ord_type` has been renamed to `clippy::non_canonical_partial_ord_impl` - --> tests/ui/rename.rs:72:9 + --> tests/ui/rename.rs:73:9 | LL | #![warn(clippy::incorrect_partial_ord_impl_on_ord_type)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::non_canonical_partial_ord_impl` error: lint `clippy::integer_arithmetic` has been renamed to `clippy::arithmetic_side_effects` - --> tests/ui/rename.rs:73:9 + --> tests/ui/rename.rs:74:9 | LL | #![warn(clippy::integer_arithmetic)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::arithmetic_side_effects` error: lint `clippy::logic_bug` has been renamed to `clippy::overly_complex_bool_expr` - --> tests/ui/rename.rs:74:9 + --> tests/ui/rename.rs:75:9 | LL | #![warn(clippy::logic_bug)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::overly_complex_bool_expr` error: lint `clippy::new_without_default_derive` has been renamed to `clippy::new_without_default` - --> tests/ui/rename.rs:75:9 + --> tests/ui/rename.rs:76:9 | LL | #![warn(clippy::new_without_default_derive)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::new_without_default` error: lint `clippy::option_and_then_some` has been renamed to `clippy::bind_instead_of_map` - --> tests/ui/rename.rs:76:9 + --> tests/ui/rename.rs:77:9 | LL | #![warn(clippy::option_and_then_some)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::bind_instead_of_map` error: lint `clippy::option_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:77:9 + --> tests/ui/rename.rs:78:9 | LL | #![warn(clippy::option_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::option_map_unwrap_or` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:78:9 + --> tests/ui/rename.rs:79:9 | LL | #![warn(clippy::option_map_unwrap_or)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:79:9 + --> tests/ui/rename.rs:80:9 | LL | #![warn(clippy::option_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::option_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:80:9 + --> tests/ui/rename.rs:81:9 | LL | #![warn(clippy::option_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` +error: lint `clippy::overflow_check_conditional` has been renamed to `clippy::panicking_overflow_checks` + --> tests/ui/rename.rs:82:9 + | +LL | #![warn(clippy::overflow_check_conditional)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::panicking_overflow_checks` + error: lint `clippy::ref_in_deref` has been renamed to `clippy::needless_borrow` - --> tests/ui/rename.rs:81:9 + --> tests/ui/rename.rs:83:9 | LL | #![warn(clippy::ref_in_deref)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::needless_borrow` error: lint `clippy::result_expect_used` has been renamed to `clippy::expect_used` - --> tests/ui/rename.rs:82:9 + --> tests/ui/rename.rs:84:9 | LL | #![warn(clippy::result_expect_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::expect_used` error: lint `clippy::result_map_unwrap_or_else` has been renamed to `clippy::map_unwrap_or` - --> tests/ui/rename.rs:83:9 + --> tests/ui/rename.rs:85:9 | LL | #![warn(clippy::result_map_unwrap_or_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::map_unwrap_or` error: lint `clippy::result_unwrap_used` has been renamed to `clippy::unwrap_used` - --> tests/ui/rename.rs:84:9 + --> tests/ui/rename.rs:86:9 | LL | #![warn(clippy::result_unwrap_used)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_used` error: lint `clippy::single_char_push_str` has been renamed to `clippy::single_char_add_str` - --> tests/ui/rename.rs:85:9 + --> tests/ui/rename.rs:87:9 | LL | #![warn(clippy::single_char_push_str)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::single_char_add_str` error: lint `clippy::stutter` has been renamed to `clippy::module_name_repetitions` - --> tests/ui/rename.rs:86:9 + --> tests/ui/rename.rs:88:9 | LL | #![warn(clippy::stutter)] | ^^^^^^^^^^^^^^^ help: use the new name: `clippy::module_name_repetitions` error: lint `clippy::thread_local_initializer_can_be_made_const` has been renamed to `clippy::missing_const_for_thread_local` - --> tests/ui/rename.rs:87:9 + --> tests/ui/rename.rs:89:9 | LL | #![warn(clippy::thread_local_initializer_can_be_made_const)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::missing_const_for_thread_local` error: lint `clippy::to_string_in_display` has been renamed to `clippy::recursive_format_impl` - --> tests/ui/rename.rs:88:9 + --> tests/ui/rename.rs:90:9 | LL | #![warn(clippy::to_string_in_display)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::recursive_format_impl` error: lint `clippy::unwrap_or_else_default` has been renamed to `clippy::unwrap_or_default` - --> tests/ui/rename.rs:89:9 + --> tests/ui/rename.rs:91:9 | LL | #![warn(clippy::unwrap_or_else_default)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::unwrap_or_default` error: lint `clippy::zero_width_space` has been renamed to `clippy::invisible_characters` - --> tests/ui/rename.rs:90:9 + --> tests/ui/rename.rs:92:9 | LL | #![warn(clippy::zero_width_space)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::invisible_characters` error: lint `clippy::cast_ref_to_mut` has been renamed to `invalid_reference_casting` - --> tests/ui/rename.rs:91:9 + --> tests/ui/rename.rs:93:9 | LL | #![warn(clippy::cast_ref_to_mut)] | ^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_reference_casting` error: lint `clippy::clone_double_ref` has been renamed to `suspicious_double_ref_op` - --> tests/ui/rename.rs:92:9 + --> tests/ui/rename.rs:94:9 | LL | #![warn(clippy::clone_double_ref)] | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `suspicious_double_ref_op` error: lint `clippy::cmp_nan` has been renamed to `invalid_nan_comparisons` - --> tests/ui/rename.rs:93:9 + --> tests/ui/rename.rs:95:9 | LL | #![warn(clippy::cmp_nan)] | ^^^^^^^^^^^^^^^ help: use the new name: `invalid_nan_comparisons` error: lint `clippy::drop_bounds` has been renamed to `drop_bounds` - --> tests/ui/rename.rs:94:9 + --> tests/ui/rename.rs:96:9 | LL | #![warn(clippy::drop_bounds)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `drop_bounds` error: lint `clippy::drop_copy` has been renamed to `dropping_copy_types` - --> tests/ui/rename.rs:95:9 + --> tests/ui/rename.rs:97:9 | LL | #![warn(clippy::drop_copy)] | ^^^^^^^^^^^^^^^^^ help: use the new name: `dropping_copy_types` error: lint `clippy::drop_ref` has been renamed to `dropping_references` - --> tests/ui/rename.rs:96:9 + --> tests/ui/rename.rs:98:9 | LL | #![warn(clippy::drop_ref)] | ^^^^^^^^^^^^^^^^ help: use the new name: `dropping_references` error: lint `clippy::fn_null_check` has been renamed to `useless_ptr_null_checks` - --> tests/ui/rename.rs:97:9 + --> tests/ui/rename.rs:99:9 | LL | #![warn(clippy::fn_null_check)] | ^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `useless_ptr_null_checks` error: lint `clippy::for_loop_over_option` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:98:9 + --> tests/ui/rename.rs:100:9 | LL | #![warn(clippy::for_loop_over_option)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loop_over_result` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:99:9 + --> tests/ui/rename.rs:101:9 | LL | #![warn(clippy::for_loop_over_result)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::for_loops_over_fallibles` has been renamed to `for_loops_over_fallibles` - --> tests/ui/rename.rs:100:9 + --> tests/ui/rename.rs:102:9 | LL | #![warn(clippy::for_loops_over_fallibles)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `for_loops_over_fallibles` error: lint `clippy::forget_copy` has been renamed to `forgetting_copy_types` - --> tests/ui/rename.rs:101:9 + --> tests/ui/rename.rs:103:9 | LL | #![warn(clippy::forget_copy)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_copy_types` error: lint `clippy::forget_ref` has been renamed to `forgetting_references` - --> tests/ui/rename.rs:102:9 + --> tests/ui/rename.rs:104:9 | LL | #![warn(clippy::forget_ref)] | ^^^^^^^^^^^^^^^^^^ help: use the new name: `forgetting_references` error: lint `clippy::into_iter_on_array` has been renamed to `array_into_iter` - --> tests/ui/rename.rs:103:9 + --> tests/ui/rename.rs:105:9 | LL | #![warn(clippy::into_iter_on_array)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `array_into_iter` error: lint `clippy::invalid_atomic_ordering` has been renamed to `invalid_atomic_ordering` - --> tests/ui/rename.rs:104:9 + --> tests/ui/rename.rs:106:9 | LL | #![warn(clippy::invalid_atomic_ordering)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_atomic_ordering` error: lint `clippy::invalid_ref` has been renamed to `invalid_value` - --> tests/ui/rename.rs:105:9 + --> tests/ui/rename.rs:107:9 | LL | #![warn(clippy::invalid_ref)] | ^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_value` error: lint `clippy::invalid_utf8_in_unchecked` has been renamed to `invalid_from_utf8_unchecked` - --> tests/ui/rename.rs:106:9 + --> tests/ui/rename.rs:108:9 | LL | #![warn(clippy::invalid_utf8_in_unchecked)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `invalid_from_utf8_unchecked` error: lint `clippy::let_underscore_drop` has been renamed to `let_underscore_drop` - --> tests/ui/rename.rs:107:9 + --> tests/ui/rename.rs:109:9 | LL | #![warn(clippy::let_underscore_drop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `let_underscore_drop` error: lint `clippy::mem_discriminant_non_enum` has been renamed to `enum_intrinsics_non_enums` - --> tests/ui/rename.rs:108:9 + --> tests/ui/rename.rs:110:9 | LL | #![warn(clippy::mem_discriminant_non_enum)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `enum_intrinsics_non_enums` error: lint `clippy::panic_params` has been renamed to `non_fmt_panics` - --> tests/ui/rename.rs:109:9 + --> tests/ui/rename.rs:111:9 | LL | #![warn(clippy::panic_params)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `non_fmt_panics` error: lint `clippy::positional_named_format_parameters` has been renamed to `named_arguments_used_positionally` - --> tests/ui/rename.rs:110:9 + --> tests/ui/rename.rs:112:9 | LL | #![warn(clippy::positional_named_format_parameters)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `named_arguments_used_positionally` error: lint `clippy::temporary_cstring_as_ptr` has been renamed to `temporary_cstring_as_ptr` - --> tests/ui/rename.rs:111:9 + --> tests/ui/rename.rs:113:9 | LL | #![warn(clippy::temporary_cstring_as_ptr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `temporary_cstring_as_ptr` error: lint `clippy::undropped_manually_drops` has been renamed to `undropped_manually_drops` - --> tests/ui/rename.rs:112:9 + --> tests/ui/rename.rs:114:9 | LL | #![warn(clippy::undropped_manually_drops)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `undropped_manually_drops` error: lint `clippy::unknown_clippy_lints` has been renamed to `unknown_lints` - --> tests/ui/rename.rs:113:9 + --> tests/ui/rename.rs:115:9 | LL | #![warn(clippy::unknown_clippy_lints)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unknown_lints` error: lint `clippy::unused_label` has been renamed to `unused_labels` - --> tests/ui/rename.rs:114:9 + --> tests/ui/rename.rs:116:9 | LL | #![warn(clippy::unused_label)] | ^^^^^^^^^^^^^^^^^^^^ help: use the new name: `unused_labels` error: lint `clippy::vtable_address_comparisons` has been renamed to `ambiguous_wide_pointer_comparisons` - --> tests/ui/rename.rs:115:9 + --> tests/ui/rename.rs:117:9 | LL | #![warn(clippy::vtable_address_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `ambiguous_wide_pointer_comparisons` -error: aborting due to 59 previous errors +error: aborting due to 60 previous errors From d2ff2b98507d0abaa2c878f74035774f9d9bcd95 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 16 Jun 2024 20:53:10 -0400 Subject: [PATCH 065/107] Move `panicking_overflow_checks` into `correctness` and clean up docs. --- clippy_lints/src/panicking_overflow_checks.rs | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/panicking_overflow_checks.rs b/clippy_lints/src/panicking_overflow_checks.rs index c9c2935b3ae44..7f100a746d5ed 100644 --- a/clippy_lints/src/panicking_overflow_checks.rs +++ b/clippy_lints/src/panicking_overflow_checks.rs @@ -8,22 +8,42 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Detects classic underflow/overflow checks. + /// Detects C-style underflow/overflow checks. /// /// ### Why is this bad? - /// Most classic C underflow/overflow checks will fail in - /// Rust. Users can use functions like `overflowing_*` and `wrapping_*` instead. + /// These checks will, by default, panic in debug builds rather than check + /// whether the operation caused an overflow. /// /// ### Example /// ```no_run - /// # let a = 1; - /// # let b = 2; - /// a + b < a; + /// # let a = 1i32; + /// # let b = 2i32; + /// if a + b < a { + /// // handle overflow + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// # let a = 1i32; + /// # let b = 2i32; + /// if a.checked_add(b).is_none() { + /// // handle overflow + /// } + /// ``` + /// + /// Or: + /// ```no_run + /// # let a = 1i32; + /// # let b = 2i32; + /// if a.overflowing_add(b).1 { + /// // handle overflow + /// } /// ``` #[clippy::version = "pre 1.29.0"] pub PANICKING_OVERFLOW_CHECKS, - complexity, - "overflow checks inspired by C which are likely to panic" + correctness, + "overflow checks which will panic in debug mode" } declare_lint_pass!(PanickingOverflowChecks => [PANICKING_OVERFLOW_CHECKS]); From 791ff403373c36822c062a1b05e5898fdc44ef4a Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:30:28 +0000 Subject: [PATCH 066/107] Add support for `mir::TerminatorKind::TailCall` in clippy --- clippy_utils/src/qualify_min_const_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 42b10f69c0cdf..f206b2ceebcb1 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -330,7 +330,8 @@ fn check_terminator<'tcx>( target: _, unwind: _, fn_span: _, - } => { + } + | TerminatorKind::TailCall { func, args, fn_span: _ } => { let fn_ty = func.ty(body, tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { if !is_const_fn(tcx, fn_def_id, msrv) { From 93e74a4187688d4948b57fcf44e44d192c5d6357 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 7 Jul 2024 18:40:44 +0200 Subject: [PATCH 067/107] Lintcheck: Update lintcheck documentation --- lintcheck/README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lintcheck/README.md b/lintcheck/README.md index 2d6039caeef07..47a96e0a03c9c 100644 --- a/lintcheck/README.md +++ b/lintcheck/README.md @@ -7,13 +7,13 @@ repo. We can then check the diff and spot new or disappearing warnings. From the repo root, run: ``` -cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml +cargo lintcheck ``` or ``` -cargo lintcheck +cargo run --target-dir lintcheck/target --manifest-path lintcheck/Cargo.toml ``` By default, the logs will be saved into @@ -33,6 +33,8 @@ the 200 recently most downloaded crates: cargo lintcheck popular -n 200 custom.toml ``` +> Note: Lintcheck isn't sandboxed. Only use it to check crates that you trust or +> sandbox it manually. ### Configuring the Crate Sources @@ -65,17 +67,11 @@ sources. #### Command Line Options (optional) ```toml -bitflags = {name = "bitflags", versions = ['1.2.1'], options = ['-Wclippy::pedantic', '-Wclippy::cargo']} +clap = {name = "clap", versions = ['4.5.8'], options = ['-Fderive']} ``` It is possible to specify command line options for each crate. This makes it -possible to only check a crate for certain lint groups. If no options are -specified, the lint groups `clippy::all`, `clippy::pedantic`, and -`clippy::cargo` are checked. If an empty array is specified only `clippy::all` -is checked. - -**Note:** `-Wclippy::all` is always enabled by default, unless `-Aclippy::all` -is explicitly specified in the options. +possible to enable or disable features. ### Fix mode You can run `cargo lintcheck --fix` which will run Clippy with `--fix` and From 8e2ddc800b2e89d3a5df1dd9373ddea6e8d9fe42 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 13:01:12 -0400 Subject: [PATCH 068/107] `inherent_to_string`: Check HIR tree before checking for macros. --- clippy_lints/src/inherent_to_string.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 9aedf5ec7e85f..ec6174bc0301a 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -91,10 +91,6 @@ declare_lint_pass!(InherentToString => [INHERENT_TO_STRING, INHERENT_TO_STRING_S impl<'tcx> LateLintPass<'tcx> for InherentToString { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { - if impl_item.span.from_expansion() { - return; - } - // Check if item is a method called `to_string` and has a parameter 'self' if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // #11201 @@ -106,6 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { && decl.implicit_self.has_implicit_self() && decl.inputs.len() == 1 && impl_item.generics.params.iter().all(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) + && !impl_item.span.from_expansion() // Check if return type is String && is_type_lang_item(cx, return_ty(cx, impl_item.owner_id), LangItem::String) // Filters instances of to_string which are required by a trait From dc8403f0f5fe3c3fab63766c70e5a2640baa1243 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 13:27:16 -0400 Subject: [PATCH 069/107] `inline_fn_without_body`: inline into a single function --- clippy_lints/src/inline_fn_without_body.rs | 41 ++++++++++------------ 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/inline_fn_without_body.rs b/clippy_lints/src/inline_fn_without_body.rs index 860258fd030e0..5657c58bb0a47 100644 --- a/clippy_lints/src/inline_fn_without_body.rs +++ b/clippy_lints/src/inline_fn_without_body.rs @@ -2,12 +2,11 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::sugg::DiagExt; -use rustc_ast::ast::Attribute; use rustc_errors::Applicability; use rustc_hir::{TraitFn, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::{sym, Symbol}; +use rustc_span::sym; declare_clippy_lint! { /// ### What it does @@ -34,27 +33,23 @@ declare_lint_pass!(InlineFnWithoutBody => [INLINE_FN_WITHOUT_BODY]); impl<'tcx> LateLintPass<'tcx> for InlineFnWithoutBody { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind { - let attrs = cx.tcx.hir().attrs(item.hir_id()); - check_attrs(cx, item.ident.name, attrs); + if let TraitItemKind::Fn(_, TraitFn::Required(_)) = item.kind + && let Some(attr) = cx + .tcx + .hir() + .attrs(item.hir_id()) + .iter() + .find(|a| a.has_name(sym::inline)) + { + span_lint_and_then( + cx, + INLINE_FN_WITHOUT_BODY, + attr.span, + format!("use of `#[inline]` on trait method `{}` which has no body", item.ident), + |diag| { + diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable); + }, + ); } } } - -fn check_attrs(cx: &LateContext<'_>, name: Symbol, attrs: &[Attribute]) { - for attr in attrs { - if !attr.has_name(sym::inline) { - continue; - } - - span_lint_and_then( - cx, - INLINE_FN_WITHOUT_BODY, - attr.span, - format!("use of `#[inline]` on trait method `{name}` which has no body"), - |diag| { - diag.suggest_remove_item(cx, attr.span, "remove", Applicability::MachineApplicable); - }, - ); - } -} From aef07100544f44c6813fc811e9f0440551ce418d Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 13:33:02 -0400 Subject: [PATCH 070/107] `instant_subtraction`: Reduce redundant work. --- clippy_lints/src/instant_subtraction.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/instant_subtraction.rs b/clippy_lints/src/instant_subtraction.rs index 10b00f632bb00..5fe152d1e3011 100644 --- a/clippy_lints/src/instant_subtraction.rs +++ b/clippy_lints/src/instant_subtraction.rs @@ -85,16 +85,19 @@ impl LateLintPass<'_> for InstantSubtraction { lhs, rhs, ) = expr.kind + && let typeck = cx.typeck_results() + && ty::is_type_diagnostic_item(cx, typeck.expr_ty(lhs), sym::Instant) { + let rhs_ty = typeck.expr_ty(rhs); + if is_instant_now_call(cx, lhs) - && is_an_instant(cx, rhs) + && ty::is_type_diagnostic_item(cx, rhs_ty, sym::Instant) && let Some(sugg) = Sugg::hir_opt(cx, rhs) { print_manual_instant_elapsed_sugg(cx, expr, sugg); - } else if !expr.span.from_expansion() + } else if ty::is_type_diagnostic_item(cx, rhs_ty, sym::Duration) + && !expr.span.from_expansion() && self.msrv.meets(msrvs::TRY_FROM) - && is_an_instant(cx, lhs) - && is_a_duration(cx, rhs) { print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); } @@ -115,16 +118,6 @@ fn is_instant_now_call(cx: &LateContext<'_>, expr_block: &'_ Expr<'_>) -> bool { } } -fn is_an_instant(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expr); - ty::is_type_diagnostic_item(cx, expr_ty, sym::Instant) -} - -fn is_a_duration(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expr); - ty::is_type_diagnostic_item(cx, expr_ty, sym::Duration) -} - fn print_manual_instant_elapsed_sugg(cx: &LateContext<'_>, expr: &Expr<'_>, sugg: Sugg<'_>) { span_lint_and_sugg( cx, From ca5c2813eb7e988c4d2a25312f5749cf6686230e Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 20:14:50 -0400 Subject: [PATCH 071/107] `items_after_statements`: Do less work in the default case. --- clippy_lints/src/items_after_statements.rs | 59 +++++++++++----------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index 39223c20470b5..a88d8e24fda8d 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -54,36 +54,35 @@ declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]); impl LateLintPass<'_> for ItemsAfterStatements { fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { - if in_external_macro(cx.sess(), block.span) { - return; - } - - // skip initial items - let stmts = block - .stmts - .iter() - .skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..))); - - // lint on all further items - for stmt in stmts { - if let StmtKind::Item(item_id) = stmt.kind { - let item = cx.tcx.hir().item(item_id); - if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) { - return; - } - if let ItemKind::Macro(..) = item.kind { - // do not lint `macro_rules`, but continue processing further statements - continue; - } - span_lint_hir( - cx, - ITEMS_AFTER_STATEMENTS, - item.hir_id(), - item.span, - "adding items after statements is confusing, since items exist from the \ - start of the scope", - ); - } + if block.stmts.len() > 1 { + let ctxt = block.span.ctxt(); + let mut in_external = None; + block + .stmts + .iter() + .skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..))) + .filter_map(|stmt| match stmt.kind { + StmtKind::Item(id) => Some(cx.tcx.hir().item(id)), + _ => None, + }) + // Ignore macros since they can only see previously defined locals. + .filter(|item| !matches!(item.kind, ItemKind::Macro(..))) + // Stop linting if macros define items. + .take_while(|item| item.span.ctxt() == ctxt) + // Don't use `next` due to the complex filter chain. + .for_each(|item| { + // Only do the macro check once, but delay it until it's needed. + if !*in_external.get_or_insert_with(|| in_external_macro(cx.sess(), block.span)) { + span_lint_hir( + cx, + ITEMS_AFTER_STATEMENTS, + item.hir_id(), + item.span, + "adding items after statements is confusing, since items exist from the \ + start of the scope", + ); + } + }); } } } From fa1a690b23ea772f1ca5893f80a46ee7ae48b6a1 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 20:23:52 -0400 Subject: [PATCH 072/107] `iter_not_returning_iterator`: * Check HIR tree first. * Check name by symbol. --- .../src/iter_not_returning_iterator.rs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/clippy_lints/src/iter_not_returning_iterator.rs b/clippy_lints/src/iter_not_returning_iterator.rs index 1b5f1b499475f..ba0cd5d6eb359 100644 --- a/clippy_lints/src/iter_not_returning_iterator.rs +++ b/clippy_lints/src/iter_not_returning_iterator.rs @@ -4,7 +4,7 @@ use rustc_hir::def_id::LocalDefId; use rustc_hir::{FnSig, ImplItem, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::symbol::sym; +use rustc_span::{sym, Symbol}; declare_clippy_lint! { /// ### What it does @@ -43,30 +43,27 @@ declare_lint_pass!(IterNotReturningIterator => [ITER_NOT_RETURNING_ITERATOR]); impl<'tcx> LateLintPass<'tcx> for IterNotReturningIterator { fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - let name = item.ident.name.as_str(); - if matches!(name, "iter" | "iter_mut") { - if let TraitItemKind::Fn(fn_sig, _) = &item.kind { - check_sig(cx, name, fn_sig, item.owner_id.def_id); - } + if let TraitItemKind::Fn(fn_sig, _) = &item.kind + && matches!(item.ident.name, sym::iter | sym::iter_mut) + { + check_sig(cx, item.ident.name, fn_sig, item.owner_id.def_id); } } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'tcx>) { - let name = item.ident.name.as_str(); - if matches!(name, "iter" | "iter_mut") + if let ImplItemKind::Fn(fn_sig, _) = &item.kind + && matches!(item.ident.name, sym::iter | sym::iter_mut) && !matches!( cx.tcx.parent_hir_node(item.hir_id()), Node::Item(Item { kind: ItemKind::Impl(i), .. }) if i.of_trait.is_some() ) { - if let ImplItemKind::Fn(fn_sig, _) = &item.kind { - check_sig(cx, name, fn_sig, item.owner_id.def_id); - } + check_sig(cx, item.ident.name, fn_sig, item.owner_id.def_id); } } } -fn check_sig(cx: &LateContext<'_>, name: &str, sig: &FnSig<'_>, fn_id: LocalDefId) { +fn check_sig(cx: &LateContext<'_>, name: Symbol, sig: &FnSig<'_>, fn_id: LocalDefId) { if sig.decl.implicit_self.has_implicit_self() { let ret_ty = cx .tcx From 6b10b4360cbecf11131228d174e6922505b215a1 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 20:25:45 -0400 Subject: [PATCH 073/107] `iter_without_into_iter`: Delay macro check --- clippy_lints/src/iter_without_into_iter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 6b03f2597b083..1e6404190d3cf 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -125,13 +125,13 @@ fn is_ty_exported(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { impl LateLintPass<'_> for IterWithoutIntoIter { fn check_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::Item<'_>) { - if !in_external_macro(cx.sess(), item.span) - && let ItemKind::Impl(imp) = item.kind + if let ItemKind::Impl(imp) = item.kind && let TyKind::Ref(_, self_ty_without_ref) = &imp.self_ty.kind && let Some(trait_ref) = imp.of_trait && trait_ref .trait_def_id() .is_some_and(|did| cx.tcx.is_diagnostic_item(sym::IntoIterator, did)) + && !in_external_macro(cx.sess(), item.span) && let &ty::Ref(_, ty, mtbl) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind() && let expected_method_name = match mtbl { Mutability::Mut => sym::iter_mut, From 3092c8a5fd4d715dd58bd8f6843330c3aa8de768 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 22:14:08 -0400 Subject: [PATCH 074/107] `large_const_arrays`: Check HIR tree first. --- clippy_lints/src/large_const_arrays.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 7f8197c0cc01a..b18ab625e6091 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -46,12 +46,12 @@ impl_lint_pass!(LargeConstArrays => [LARGE_CONST_ARRAYS]); impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if !item.span.from_expansion() - && let ItemKind::Const(_, generics, _) = &item.kind + if let ItemKind::Const(_, generics, _) = &item.kind // Since static items may not have generics, skip generic const items. // FIXME(generic_const_items): I don't think checking `generics.hwcp` suffices as it // doesn't account for empty where-clauses that only consist of keyword `where` IINM. && generics.params.is_empty() && !generics.has_where_clause_predicates + && !item.span.from_expansion() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() From 430c02cbd0f45d50fd1c4bbefad4d19b5d464984 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 22:16:47 -0400 Subject: [PATCH 075/107] `large_enum_variant`: Delay macro check --- clippy_lints/src/large_enum_variant.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 0bf7389ef9cc8..85daadcc53734 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -77,17 +77,12 @@ impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]); impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) { - if in_external_macro(cx.tcx.sess, item.span) { - return; - } - if let ItemKind::Enum(ref def, _) = item.kind { - let ty = cx.tcx.type_of(item.owner_id).instantiate_identity(); - let ty::Adt(adt, subst) = ty.kind() else { - panic!("already checked whether this is an enum") - }; - if adt.variants().len() <= 1 { - return; - } + if let ItemKind::Enum(ref def, _) = item.kind + && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() + && let ty::Adt(adt, subst) = ty.kind() + && adt.variants().len() > 1 + && !in_external_macro(cx.tcx.sess, item.span) + { let variants_size = AdtVariantInfo::new(cx, *adt, subst); let mut difference = variants_size[0].size - variants_size[1].size; From c3dd028d3e304e59da3ac32dca1190672cbe705f Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 22:42:27 -0400 Subject: [PATCH 076/107] `large_futures`: Delay macro check --- clippy_lints/src/large_futures.rs | 43 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/clippy_lints/src/large_futures.rs b/clippy_lints/src/large_futures.rs index 07488a512a37e..602227e42490a 100644 --- a/clippy_lints/src/large_futures.rs +++ b/clippy_lints/src/large_futures.rs @@ -54,29 +54,26 @@ impl_lint_pass!(LargeFuture => [LARGE_FUTURES]); impl<'tcx> LateLintPass<'tcx> for LargeFuture { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) { - return; - } - if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind { - if let ExprKind::Call(func, [expr, ..]) = expr.kind - && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind - && let ty = cx.typeck_results().expr_ty(expr) - && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() - && implements_trait(cx, ty, future_trait_def_id, &[]) - && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) - && let size = layout.layout.size() - && size >= Size::from_bytes(self.future_size_threshold) - { - span_lint_and_sugg( - cx, - LARGE_FUTURES, - expr.span, - format!("large future with a size of {} bytes", size.bytes()), - "consider `Box::pin` on it", - format!("Box::pin({})", snippet(cx, expr.span, "..")), - Applicability::Unspecified, - ); - } + if let ExprKind::Match(scrutinee, _, MatchSource::AwaitDesugar) = expr.kind + && let ExprKind::Call(func, [arg, ..]) = scrutinee.kind + && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind + && !expr.span.from_expansion() + && let ty = cx.typeck_results().expr_ty(arg) + && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait() + && implements_trait(cx, ty, future_trait_def_id, &[]) + && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) + && let size = layout.layout.size() + && size >= Size::from_bytes(self.future_size_threshold) + { + span_lint_and_sugg( + cx, + LARGE_FUTURES, + arg.span, + format!("large future with a size of {} bytes", size.bytes()), + "consider `Box::pin` on it", + format!("Box::pin({})", snippet(cx, arg.span, "..")), + Applicability::Unspecified, + ); } } } From 03036c11b3390dc216f59b014d0837b591460821 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 22:46:27 -0400 Subject: [PATCH 077/107] `large_include_file`: Check HIR tree first. --- clippy_lints/src/large_include_file.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 07efee159aab4..2688283a6ce8d 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,5 +1,4 @@ use clippy_utils::diagnostics::span_lint_and_note; -use clippy_utils::is_lint_allowed; use clippy_utils::macros::root_macro_call_first_node; use rustc_ast::LitKind; use rustc_hir::{Expr, ExprKind}; @@ -52,24 +51,19 @@ impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); impl LateLintPass<'_> for LargeIncludeFile { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { - if let Some(macro_call) = root_macro_call_first_node(cx, expr) - && !is_lint_allowed(cx, LARGE_INCLUDE_FILE, expr.hir_id) - && (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 ExprKind::Lit(lit) = &expr.kind - { - let len = match &lit.node { + if let ExprKind::Lit(lit) = &expr.kind + && let len = match &lit.node { // include_bytes LitKind::ByteStr(bstr, _) => bstr.len(), // include_str LitKind::Str(sym, _) => sym.as_str().len(), _ => return, - }; - - if len as u64 <= self.max_file_size { - return; } - + && 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)) + { span_lint_and_note( cx, LARGE_INCLUDE_FILE, From 5ae33c76994cf67952034c814c0df7719b4db585 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 22:50:13 -0400 Subject: [PATCH 078/107] `legacy_numeric_constants`: Check HIR tree first. --- clippy_lints/src/legacy_numeric_constants.rs | 46 ++++++++------------ 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/legacy_numeric_constants.rs b/clippy_lints/src/legacy_numeric_constants.rs index eadfeb6e34181..a08b40bef3722 100644 --- a/clippy_lints/src/legacy_numeric_constants.rs +++ b/clippy_lints/src/legacy_numeric_constants.rs @@ -48,15 +48,11 @@ impl_lint_pass!(LegacyNumericConstants => [LEGACY_NUMERIC_CONSTANTS]); impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - let Self { msrv } = self; - - if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), item.span) { - return; - } - // Integer modules are "TBD" deprecated, and the contents are too, // so lint on the `use` statement directly. if let ItemKind::Use(path, kind @ (UseKind::Single | UseKind::Glob)) = item.kind + && self.msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) + && !in_external_macro(cx.sess(), item.span) && let Some(def_id) = path.res[0].opt_def_id() { let module = if is_integer_module(cx, def_id) { @@ -103,12 +99,7 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - let Self { msrv } = self; - - if !msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) || in_external_macro(cx.sess(), expr.span) { - return; - } - let ExprKind::Path(qpath) = expr.kind else { + let ExprKind::Path(qpath) = &expr.kind else { return; }; @@ -129,10 +120,10 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { ) // `::xxx_value` check } else if let QPath::TypeRelative(_, last_segment) = qpath - && let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() - && is_integer_method(cx, def_id) + && let Some(def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id() && let Some(par_expr) = get_parent_expr(cx, expr) - && let ExprKind::Call(_, _) = par_expr.kind + && let ExprKind::Call(_, []) = par_expr.kind + && is_integer_method(cx, def_id) { let name = last_segment.ident.name.as_str(); @@ -145,19 +136,20 @@ impl<'tcx> LateLintPass<'tcx> for LegacyNumericConstants { return; }; - if is_from_proc_macro(cx, expr) { - return; + if self.msrv.meets(NUMERIC_ASSOCIATED_CONSTANTS) + && !in_external_macro(cx.sess(), expr.span) + && !is_from_proc_macro(cx, expr) + { + span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| { + diag.span_suggestion_with_style( + span, + "use the associated constant instead", + sugg, + Applicability::MaybeIncorrect, + SuggestionStyle::ShowAlways, + ); + }); } - - span_lint_hir_and_then(cx, LEGACY_NUMERIC_CONSTANTS, expr.hir_id, span, msg, |diag| { - diag.span_suggestion_with_style( - span, - "use the associated constant instead", - sugg, - Applicability::MaybeIncorrect, - SuggestionStyle::ShowAlways, - ); - }); } extract_msrv_attr!(LateContext); From eac1aab1ff6900ba7aabb365eee0aaaad96be57f Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 7 Jul 2024 20:14:21 +0000 Subject: [PATCH 079/107] Reduce the size of lintcheck JSON output --- lintcheck/Cargo.toml | 1 + lintcheck/src/driver.rs | 2 - lintcheck/src/json.rs | 99 ++++++++++++++++++-------------------- lintcheck/src/main.rs | 31 +++++------- lintcheck/src/recursive.rs | 4 +- 5 files changed, 61 insertions(+), 76 deletions(-) diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index ae9e77b8eed05..3c86dfe324f4d 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -16,6 +16,7 @@ clap = { version = "4.4", features = ["derive", "env"] } crossbeam-channel = "0.5.6" diff = "0.1.13" flate2 = "1.0" +itertools = "0.12" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" diff --git a/lintcheck/src/driver.rs b/lintcheck/src/driver.rs index 47724a2fedb00..041be5081f28c 100644 --- a/lintcheck/src/driver.rs +++ b/lintcheck/src/driver.rs @@ -11,8 +11,6 @@ use std::{env, mem}; fn run_clippy(addr: &str) -> Option { let driver_info = DriverInfo { package_name: env::var("CARGO_PKG_NAME").ok()?, - crate_name: env::var("CARGO_CRATE_NAME").ok()?, - version: env::var("CARGO_PKG_VERSION").ok()?, }; let mut stream = BufReader::new(TcpStream::connect(addr).unwrap()); diff --git a/lintcheck/src/json.rs b/lintcheck/src/json.rs index 43d0413c7ceeb..1a652927988e1 100644 --- a/lintcheck/src/json.rs +++ b/lintcheck/src/json.rs @@ -1,37 +1,50 @@ -use std::collections::HashMap; -use std::fmt::Write; use std::fs; -use std::hash::Hash; use std::path::Path; +use itertools::EitherOrBoth; +use serde::{Deserialize, Serialize}; + use crate::ClippyWarning; -/// Creates the log file output for [`crate::config::OutputFormat::Json`] -pub(crate) fn output(clippy_warnings: &[ClippyWarning]) -> String { - serde_json::to_string(&clippy_warnings).unwrap() +#[derive(Deserialize, Serialize)] +struct LintJson { + lint: String, + file_name: String, + byte_pos: (u32, u32), + rendered: String, } -fn load_warnings(path: &Path) -> Vec { - let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - - serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display())) +impl LintJson { + fn key(&self) -> impl Ord + '_ { + (self.file_name.as_str(), self.byte_pos, self.lint.as_str()) + } } -/// Group warnings by their primary span location + lint name -fn create_map(warnings: &[ClippyWarning]) -> HashMap> { - let mut map = HashMap::<_, Vec<_>>::with_capacity(warnings.len()); - - for warning in warnings { - let span = warning.span(); - let key = (&warning.lint_type, &span.file_name, span.byte_start, span.byte_end); +/// Creates the log file output for [`crate::config::OutputFormat::Json`] +pub(crate) fn output(clippy_warnings: Vec) -> String { + let mut lints: Vec = clippy_warnings + .into_iter() + .map(|warning| { + let span = warning.span(); + LintJson { + file_name: span.file_name.clone(), + byte_pos: (span.byte_start, span.byte_end), + lint: warning.lint, + rendered: warning.diag.rendered.unwrap(), + } + }) + .collect(); + lints.sort_by(|a, b| a.key().cmp(&b.key())); + serde_json::to_string(&lints).unwrap() +} - map.entry(key).or_default().push(warning); - } +fn load_warnings(path: &Path) -> Vec { + let file = fs::read(path).unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - map + serde_json::from_slice(&file).unwrap_or_else(|e| panic!("failed to deserialize {}: {e}", path.display())) } -fn print_warnings(title: &str, warnings: &[&ClippyWarning]) { +fn print_warnings(title: &str, warnings: &[LintJson]) { if warnings.is_empty() { return; } @@ -39,31 +52,20 @@ fn print_warnings(title: &str, warnings: &[&ClippyWarning]) { println!("### {title}"); println!("```"); for warning in warnings { - print!("{}", warning.diag); + print!("{}", warning.rendered); } println!("```"); } -fn print_changed_diff(changed: &[(&[&ClippyWarning], &[&ClippyWarning])]) { - fn render(warnings: &[&ClippyWarning]) -> String { - let mut rendered = String::new(); - for warning in warnings { - write!(&mut rendered, "{}", warning.diag).unwrap(); - } - rendered - } - +fn print_changed_diff(changed: &[(LintJson, LintJson)]) { if changed.is_empty() { return; } println!("### Changed"); println!("```diff"); - for &(old, new) in changed { - let old_rendered = render(old); - let new_rendered = render(new); - - for change in diff::lines(&old_rendered, &new_rendered) { + for (old, new) in changed { + for change in diff::lines(&old.rendered, &new.rendered) { use diff::Result::{Both, Left, Right}; match change { @@ -86,26 +88,19 @@ pub(crate) fn diff(old_path: &Path, new_path: &Path) { let old_warnings = load_warnings(old_path); let new_warnings = load_warnings(new_path); - let old_map = create_map(&old_warnings); - let new_map = create_map(&new_warnings); - let mut added = Vec::new(); let mut removed = Vec::new(); let mut changed = Vec::new(); - for (key, new) in &new_map { - if let Some(old) = old_map.get(key) { - if old != new { - changed.push((old.as_slice(), new.as_slice())); - } - } else { - added.extend(new); - } - } - - for (key, old) in &old_map { - if !new_map.contains_key(key) { - removed.extend(old); + for change in itertools::merge_join_by(old_warnings, new_warnings, |old, new| old.key().cmp(&new.key())) { + match change { + EitherOrBoth::Both(old, new) => { + if old.rendered != new.rendered { + changed.push((old, new)); + } + }, + EitherOrBoth::Left(old) => removed.push(old), + EitherOrBoth::Right(new) => added.push(new), } } diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 70b8af0fdb95c..26a67beb4427b 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -38,7 +38,7 @@ use std::{env, fs, thread}; use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan}; use cargo_metadata::Message; use rayon::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use walkdir::{DirEntry, WalkDir}; const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; @@ -142,19 +142,17 @@ impl RustcIce { } /// A single warning that clippy issued while checking a `Crate` -#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug)] struct ClippyWarning { - crate_name: String, - crate_version: String, - lint_type: String, + lint: String, diag: Diagnostic, } #[allow(unused)] impl ClippyWarning { - fn new(mut diag: Diagnostic, crate_name: &str, crate_version: &str) -> Option { - let lint_type = diag.code.clone()?.code; - if !(lint_type.contains("clippy") || diag.message.contains("clippy")) + fn new(mut diag: Diagnostic) -> Option { + let lint = diag.code.clone()?.code; + if !(lint.contains("clippy") || diag.message.contains("clippy")) || diag.message.contains("could not read cargo metadata") { return None; @@ -164,12 +162,7 @@ impl ClippyWarning { let rendered = diag.rendered.as_mut().unwrap(); *rendered = strip_ansi_escapes::strip_str(&rendered); - Some(Self { - crate_name: crate_name.to_owned(), - crate_version: crate_version.to_owned(), - lint_type, - diag, - }) + Some(Self { lint, diag }) } fn span(&self) -> &DiagnosticSpan { @@ -181,7 +174,7 @@ impl ClippyWarning { let mut file = span.file_name.clone(); let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end); match format { - OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint_type, self.diag.message), + OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint, self.diag.message), OutputFormat::Markdown => { if file.starts_with("target") { file.insert_str(0, "../"); @@ -189,7 +182,7 @@ impl ClippyWarning { let mut output = String::from("| "); write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap(); - write!(output, r#" | `{:<50}` | "{}" |"#, self.lint_type, self.diag.message).unwrap(); + write!(output, r#" | `{:<50}` | "{}" |"#, self.lint, self.diag.message).unwrap(); output.push('\n'); output }, @@ -473,7 +466,7 @@ impl Crate { // get all clippy warnings and ICEs let mut entries: Vec = Message::parse_stream(stdout.as_bytes()) .filter_map(|msg| match msg { - Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message, &self.name, &self.version), + Ok(Message::CompilerMessage(message)) => ClippyWarning::new(message.message), _ => None, }) .map(ClippyCheckOutput::ClippyWarning) @@ -572,7 +565,7 @@ fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) let mut counter: HashMap<&String, usize> = HashMap::new(); warnings .iter() - .for_each(|wrn| *counter.entry(&wrn.lint_type).or_insert(0) += 1); + .for_each(|wrn| *counter.entry(&wrn.lint).or_insert(0) += 1); // collect into a tupled list for sorting let mut stats: Vec<(&&String, &usize)> = counter.iter().collect(); @@ -754,7 +747,7 @@ fn lintcheck(config: LintcheckConfig) { panic!("Some crates ICEd"); } - json::output(&warnings) + json::output(warnings) }, }; diff --git a/lintcheck/src/recursive.rs b/lintcheck/src/recursive.rs index 994fa3c3b239a..24dddfe65636a 100644 --- a/lintcheck/src/recursive.rs +++ b/lintcheck/src/recursive.rs @@ -19,8 +19,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Eq, Hash, PartialEq, Clone, Serialize, Deserialize)] pub(crate) struct DriverInfo { pub package_name: String, - pub crate_name: String, - pub version: String, } pub(crate) fn serialize_line(value: &T, writer: &mut W) @@ -65,7 +63,7 @@ fn process_stream( let messages = stderr .lines() .filter_map(|json_msg| serde_json::from_str::(json_msg).ok()) - .filter_map(|diag| ClippyWarning::new(diag, &driver_info.package_name, &driver_info.version)); + .filter_map(ClippyWarning::new); for message in messages { sender.send(message).unwrap(); From ff4e62d3ec08e7243f8085e60d72f296f8373812 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:05:21 -0400 Subject: [PATCH 080/107] `len_zero`: Check HIR tree first --- clippy_lints/src/len_zero.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 57e0a7aa2c7e9..4c737371bd238 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -121,11 +121,9 @@ declare_lint_pass!(LenZero => [LEN_ZERO, LEN_WITHOUT_IS_EMPTY, COMPARISON_TO_EMP impl<'tcx> LateLintPass<'tcx> for LenZero { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if item.span.from_expansion() { - return; - } - - if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind { + if let ItemKind::Trait(_, _, _, _, trait_items) = item.kind + && !item.span.from_expansion() + { check_trait_items(cx, item, trait_items); } } @@ -162,17 +160,14 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if expr.span.from_expansion() { - return; - } - if let ExprKind::Let(lt) = expr.kind - && has_is_empty(cx, lt.init) && match lt.pat.kind { PatKind::Slice([], None, []) => true, PatKind::Lit(lit) if is_empty_string(lit) => true, _ => false, } + && !expr.span.from_expansion() + && has_is_empty(cx, lt.init) { let mut applicability = Applicability::MachineApplicable; @@ -190,7 +185,9 @@ impl<'tcx> LateLintPass<'tcx> for LenZero { ); } - if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind { + if let ExprKind::Binary(Spanned { node: cmp, .. }, left, right) = expr.kind + && !expr.span.from_expansion() + { // expr.span might contains parenthesis, see issue #10529 let actual_span = span_without_enclosing_paren(cx, expr.span); match cmp { From fed75b3895397a72dfd881e44f432dd9624a76ce Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:17:08 -0400 Subject: [PATCH 081/107] `let_if_seq`: use `array_windows`. --- clippy_lints/src/let_if_seq.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index a65cb3f4dfb44..0e488cee6b74e 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -58,12 +58,10 @@ declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]); impl<'tcx> LateLintPass<'tcx> for LetIfSeq { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { - let mut it = block.stmts.iter().peekable(); - while let Some(stmt) = it.next() { - if let Some(expr) = it.peek() - && let hir::StmtKind::Let(local) = stmt.kind + for [stmt, next] in block.stmts.array_windows::<2>() { + if let hir::StmtKind::Let(local) = stmt.kind && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind - && let hir::StmtKind::Expr(if_) = expr.kind + && let hir::StmtKind::Expr(if_) = next.kind && let hir::ExprKind::If( hir::Expr { kind: hir::ExprKind::DropTemps(cond), From 5332def0fdf4a6aecc7c06bedcbb97d8f77d2607 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:18:51 -0400 Subject: [PATCH 082/107] `let_underscore`: Delay macro check. --- clippy_lints/src/let_underscore.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 9fd4f509aa473..8fa63f3e8fde0 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -139,9 +139,9 @@ const SYNC_GUARD_PATHS: [&[&str]; 3] = [ impl<'tcx> LateLintPass<'tcx> for LetUnderscore { fn check_local(&mut self, cx: &LateContext<'tcx>, local: &LetStmt<'tcx>) { if matches!(local.source, LocalSource::Normal) - && !in_external_macro(cx.tcx.sess, local.span) && let PatKind::Wild = local.pat.kind && let Some(init) = local.init + && !in_external_macro(cx.tcx.sess, local.span) { let init_ty = cx.typeck_results().expr_ty(init); let contains_sync_guard = init_ty.walk().any(|inner| match inner.unpack() { From 15f640a7cbc7c30bc04010e843ff58f3d6ea0ddd Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:20:54 -0400 Subject: [PATCH 083/107] `let_with_type_underscore`: * Delay macro check. * Use `is_from_proc_macro --- clippy_lints/src/let_with_type_underscore.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index 593b29154b426..5a11702d7ce54 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::source::snippet; +use clippy_utils::is_from_proc_macro; use rustc_hir::{LetStmt, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; @@ -25,19 +25,14 @@ declare_clippy_lint! { } declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]); -impl LateLintPass<'_> for UnderscoreTyped { - fn check_local(&mut self, cx: &LateContext<'_>, local: &LetStmt<'_>) { - if !in_external_macro(cx.tcx.sess, local.span) - && let Some(ty) = local.ty // Ensure that it has a type defined +impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx LetStmt<'_>) { + if let Some(ty) = local.ty // Ensure that it has a type defined && let TyKind::Infer = &ty.kind // that type is '_' && local.span.eq_ctxt(ty.span) + && !in_external_macro(cx.tcx.sess, local.span) + && !is_from_proc_macro(cx, ty) { - // NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized, - // this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty` - if snippet(cx, ty.span, "_").trim() != "_" { - return; - } - span_lint_and_help( cx, LET_WITH_TYPE_UNDERSCORE, From c0fa6a92f0a4a217bddb088090b12fffa9a29f72 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:28:30 -0400 Subject: [PATCH 084/107] `literal_representation`: Delay macro check. --- clippy_lints/src/literal_representation.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index d2a140a36a83f..7481543941a6b 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -233,11 +233,9 @@ impl_lint_pass!(LiteralDigitGrouping => [ impl EarlyLintPass for LiteralDigitGrouping { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if let ExprKind::Lit(lit) = expr.kind { + if let ExprKind::Lit(lit) = expr.kind + && !in_external_macro(cx.sess(), expr.span) + { self.check_lit(cx, lit, expr.span); } } @@ -448,11 +446,9 @@ impl_lint_pass!(DecimalLiteralRepresentation => [DECIMAL_LITERAL_REPRESENTATION] impl EarlyLintPass for DecimalLiteralRepresentation { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - - if let ExprKind::Lit(lit) = expr.kind { + if let ExprKind::Lit(lit) = expr.kind + && !in_external_macro(cx.sess(), expr.span) + { self.check_lit(cx, lit, expr.span); } } From b9ba340db65d70d8f36444888b140a63e05b2c71 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:34:59 -0400 Subject: [PATCH 085/107] `manual_bits`: Delay msrv check --- clippy_lints/src/manual_bits.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 24fc2b4faeacc..d9f6be6dc2b5c 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -50,13 +50,10 @@ impl_lint_pass!(ManualBits => [MANUAL_BITS]); impl<'tcx> LateLintPass<'tcx> for ManualBits { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !self.msrv.meets(msrvs::MANUAL_BITS) { - return; - } - if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind && let BinOpKind::Mul = &bin_op.node && !in_external_macro(cx.sess(), expr.span) + && self.msrv.meets(msrvs::MANUAL_BITS) && let ctxt = expr.span.ctxt() && left_expr.span.ctxt() == ctxt && right_expr.span.ctxt() == ctxt From 95348adcb9c4f44a8f4828e6137b9ee6d42a7dbe Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 23:47:56 -0400 Subject: [PATCH 086/107] `manual_float_methods`: * Check HIR tree first. * Use `partition_in_place`. --- clippy_lints/src/lib.rs | 1 + clippy_lints/src/manual_float_methods.rs | 29 +++++++++++------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fbe895fa50e82..0084fea4d6b3f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -5,6 +5,7 @@ #![feature(f16)] #![feature(if_let_guard)] #![feature(iter_intersperse)] +#![feature(iter_partition_in_place)] #![feature(let_chains)] #![feature(never_type)] #![feature(rustc_private)] diff --git a/clippy_lints/src/manual_float_methods.rs b/clippy_lints/src/manual_float_methods.rs index 89eea0b4456d4..03416ba96de73 100644 --- a/clippy_lints/src/manual_float_methods.rs +++ b/clippy_lints/src/manual_float_methods.rs @@ -82,29 +82,26 @@ impl Variant { impl<'tcx> LateLintPass<'tcx> for ManualFloatMethods { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if !in_external_macro(cx.sess(), expr.span) - && ( - matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst) - || cx.tcx.features().declared(sym!(const_float_classify)) - ) && let ExprKind::Binary(kind, lhs, rhs) = expr.kind + if let ExprKind::Binary(kind, lhs, rhs) = expr.kind && let ExprKind::Binary(lhs_kind, lhs_lhs, lhs_rhs) = lhs.kind && let ExprKind::Binary(rhs_kind, rhs_lhs, rhs_rhs) = rhs.kind // Checking all possible scenarios using a function would be a hopeless task, as we have // 16 possible alignments of constants/operands. For now, let's use `partition`. - && let (operands, constants) = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs] - .into_iter() - .partition::>, _>(|i| path_to_local(i).is_some()) - && let [first, second] = &*operands - && let Some([const_1, const_2]) = constants - .into_iter() - .map(|i| constant(cx, cx.typeck_results(), i)) - .collect::>>() - .as_deref() + && let mut exprs = [lhs_lhs, lhs_rhs, rhs_lhs, rhs_rhs] + && exprs.iter_mut().partition_in_place(|i| path_to_local(i).is_some()) == 2 + && !in_external_macro(cx.sess(), expr.span) + && ( + matches!(cx.tcx.constness(cx.tcx.hir().enclosing_body_owner(expr.hir_id)), Constness::NotConst) + || cx.tcx.features().declared(sym!(const_float_classify)) + ) + && let [first, second, const_1, const_2] = exprs + && let Some(const_1) = constant(cx, cx.typeck_results(), const_1) + && let Some(const_2) = constant(cx, cx.typeck_results(), const_2) && path_to_local(first).is_some_and(|f| path_to_local(second).is_some_and(|s| f == s)) // The actual infinity check, we also allow `NEG_INFINITY` before` INFINITY` just in // case somebody does that for some reason - && (is_infinity(const_1) && is_neg_infinity(const_2) - || is_neg_infinity(const_1) && is_infinity(const_2)) + && (is_infinity(&const_1) && is_neg_infinity(&const_2) + || is_neg_infinity(&const_1) && is_infinity(&const_2)) && let Some(local_snippet) = snippet_opt(cx, first.span) { let variant = match (kind.node, lhs_kind.node, rhs_kind.node) { From 2d9e67ea8a74f5c25f2735a7dadd94e0f6d9e79d Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 00:02:42 -0400 Subject: [PATCH 087/107] `manual_let_else`: Delay msrv and macro checks. --- clippy_lints/src/manual_let_else.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 03e4d668dd8fb..ebebdf679a863 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -49,16 +49,14 @@ declare_clippy_lint! { impl<'tcx> QuestionMark { pub(crate) fn check_manual_let_else(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) { - if !self.msrv.meets(msrvs::LET_ELSE) || in_external_macro(cx.sess(), stmt.span) { - return; - } - if let StmtKind::Let(local) = stmt.kind && let Some(init) = local.init && local.els.is_none() && local.ty.is_none() && init.span.eq_ctxt(stmt.span) && let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init) + && self.msrv.meets(msrvs::LET_ELSE) + && !in_external_macro(cx.sess(), stmt.span) { match if_let_or_match { IfLetOrMatch::IfLet(if_let_expr, let_pat, if_then, if_else, ..) => { From f46c981f3f93c59d9fd8c81aea9b03e9bce40997 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 00:05:01 -0400 Subject: [PATCH 088/107] `manual_main_separator_str`: Check HIR tree first. --- clippy_lints/src/manual_main_separator_str.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/manual_main_separator_str.rs b/clippy_lints/src/manual_main_separator_str.rs index 5732bdda7f2cf..8e8cdc3fb0764 100644 --- a/clippy_lints/src/manual_main_separator_str.rs +++ b/clippy_lints/src/manual_main_separator_str.rs @@ -47,13 +47,13 @@ impl_lint_pass!(ManualMainSeparatorStr => [MANUAL_MAIN_SEPARATOR_STR]); impl LateLintPass<'_> for ManualMainSeparatorStr { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) - && let (target, _) = peel_hir_expr_refs(expr) - && is_trait_method(cx, target, sym::ToString) - && let ExprKind::MethodCall(path, receiver, &[], _) = target.kind + let (target, _) = peel_hir_expr_refs(expr); + if let ExprKind::MethodCall(path, receiver, &[], _) = target.kind && path.ident.name == sym::to_string && let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind && let Res::Def(DefKind::Const, receiver_def_id) = path.res + && is_trait_method(cx, target, sym::ToString) + && self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) && match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR) && let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() && ty.is_str() From 2309f8e4a12021913809b8ed4b60ff41d9c586f0 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 02:27:52 -0400 Subject: [PATCH 089/107] `manual_non_exhaustive`: Delay msrv check. --- clippy_lints/src/manual_non_exhaustive.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index d2ac0ad8363e9..73a505fd73ffc 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -97,19 +97,15 @@ impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]); impl EarlyLintPass for ManualNonExhaustiveStruct { fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) { - if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) { - return; - } - - if let ast::ItemKind::Struct(variant_data, _) = &item.kind { - let (fields, delimiter) = match variant_data { + if let ast::ItemKind::Struct(variant_data, _) = &item.kind + && let (fields, delimiter) = match variant_data { ast::VariantData::Struct { fields, .. } => (&**fields, '{'), ast::VariantData::Tuple(fields, _) => (&**fields, '('), ast::VariantData::Unit(_) => return, - }; - if fields.len() <= 1 { - return; } + && fields.len() > 1 + && self.msrv.meets(msrvs::NON_EXHAUSTIVE) + { let mut iter = fields.iter().filter_map(|f| match f.vis.kind { VisibilityKind::Public => None, VisibilityKind::Inherited => Some(Ok(f)), From 44f87e8cc1bc1f95a3d942fc2ff7d47663261566 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 02:32:04 -0400 Subject: [PATCH 090/107] `manual_range_patterns`: Delay macro check. --- clippy_lints/src/manual_range_patterns.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_lints/src/manual_range_patterns.rs b/clippy_lints/src/manual_range_patterns.rs index ec60de92c4ba2..07d4abbf5cd11 100644 --- a/clippy_lints/src/manual_range_patterns.rs +++ b/clippy_lints/src/manual_range_patterns.rs @@ -76,14 +76,11 @@ impl Num { impl LateLintPass<'_> for ManualRangePatterns { fn check_pat(&mut self, cx: &LateContext<'_>, pat: &'_ rustc_hir::Pat<'_>) { - if in_external_macro(cx.sess(), pat.span) { - return; - } - // a pattern like 1 | 2 seems fine, lint if there are at least 3 alternatives // or at least one range if let PatKind::Or(pats) = pat.kind && (pats.len() >= 3 || pats.iter().any(|p| matches!(p.kind, PatKind::Range(..)))) + && !in_external_macro(cx.sess(), pat.span) { let mut min = Num::dummy(i128::MAX); let mut max = Num::dummy(i128::MIN); From 0f4cd13f666fff504ea07c7e1ab3858942878dd5 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 02:45:37 -0400 Subject: [PATCH 091/107] `manual_rem_euclid`: Check HIR tree first. --- clippy_lints/src/manual_rem_euclid.rs | 49 ++++++++++++--------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index ab9bca170cf77..b518dc26937c2 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -48,35 +48,30 @@ impl_lint_pass!(ManualRemEuclid => [MANUAL_REM_EUCLID]); impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !self.msrv.meets(msrvs::REM_EUCLID) { - return; - } - - if in_constant(cx, expr.hir_id) && !self.msrv.meets(msrvs::REM_EUCLID_CONST) { - return; - } - - if in_external_macro(cx.sess(), expr.span) { - return; - } - // (x % c + c) % c - if let ExprKind::Binary(op1, expr1, right) = expr.kind - && op1.node == BinOpKind::Rem + if let ExprKind::Binary(rem_op, rem_lhs, rem_rhs) = expr.kind + && rem_op.node == BinOpKind::Rem + && let ExprKind::Binary(add_op, add_lhs, add_rhs) = rem_lhs.kind + && add_op.node == BinOpKind::Add && let ctxt = expr.span.ctxt() - && expr1.span.ctxt() == ctxt - && let Some(const1) = check_for_unsigned_int_constant(cx, right) - && let ExprKind::Binary(op2, left, right) = expr1.kind - && op2.node == BinOpKind::Add - && let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right) - && expr2.span.ctxt() == ctxt - && let ExprKind::Binary(op3, expr3, right) = expr2.kind - && op3.node == BinOpKind::Rem - && let Some(const3) = check_for_unsigned_int_constant(cx, right) + && rem_lhs.span.ctxt() == ctxt + && rem_rhs.span.ctxt() == ctxt + && add_lhs.span.ctxt() == ctxt + && add_rhs.span.ctxt() == ctxt + && !in_external_macro(cx.sess(), expr.span) + && self.msrv.meets(msrvs::REM_EUCLID) + && (self.msrv.meets(msrvs::REM_EUCLID_CONST) || !in_constant(cx, expr.hir_id)) + && let Some(const1) = check_for_unsigned_int_constant(cx, rem_rhs) + && let Some((const2, add_other)) = check_for_either_unsigned_int_constant(cx, add_lhs, add_rhs) + && let ExprKind::Binary(rem2_op, rem2_lhs, rem2_rhs) = add_other.kind + && rem2_op.node == BinOpKind::Rem + && const1 == const2 + && let Some(hir_id) = path_to_local(rem2_lhs) + && let Some(const3) = check_for_unsigned_int_constant(cx, rem2_rhs) // Also ensures the const is nonzero since zero can't be a divisor - && const1 == const2 && const2 == const3 - && let Some(hir_id) = path_to_local(expr3) - && let Node::Pat(_) = cx.tcx.hir_node(hir_id) + && const2 == const3 + && rem2_lhs.span.ctxt() == ctxt + && rem2_rhs.span.ctxt() == ctxt { // Apply only to params or locals with annotated types match cx.tcx.parent_hir_node(hir_id) { @@ -91,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { }; let mut app = Applicability::MachineApplicable; - let rem_of = snippet_with_context(cx, expr3.span, ctxt, "_", &mut app).0; + let rem_of = snippet_with_context(cx, rem2_lhs.span, ctxt, "_", &mut app).0; span_lint_and_sugg( cx, MANUAL_REM_EUCLID, From 03117026aedde8e26d14b856caae84e76a799831 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 02:46:58 -0400 Subject: [PATCH 092/107] `manual_retain`: Remove redundant match. --- clippy_lints/src/manual_retain.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clippy_lints/src/manual_retain.rs b/clippy_lints/src/manual_retain.rs index 3ddb06a1e08a1..8f7b8abd0c383 100644 --- a/clippy_lints/src/manual_retain.rs +++ b/clippy_lints/src/manual_retain.rs @@ -70,9 +70,8 @@ impl_lint_pass!(ManualRetain => [MANUAL_RETAIN]); impl<'tcx> LateLintPass<'tcx> for ManualRetain { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if let Assign(left_expr, collect_expr, _) = &expr.kind - && let hir::ExprKind::MethodCall(seg, ..) = &collect_expr.kind + && let hir::ExprKind::MethodCall(seg, target_expr, [], _) = &collect_expr.kind && seg.args.is_none() - && let hir::ExprKind::MethodCall(_, target_expr, [], _) = &collect_expr.kind && let Some(collect_def_id) = cx.typeck_results().type_dependent_def_id(collect_expr.hir_id) && cx.tcx.is_diagnostic_item(sym::iterator_collect_fn, collect_def_id) { From c0068ba5acad7c74111006e90ca2d310d111129c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 02:48:08 -0400 Subject: [PATCH 093/107] `manual_slice_size_calculation`: Delay constant check. --- clippy_lints/src/manual_slice_size_calculation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs index 1de686dbcb51e..429ee2637c2fa 100644 --- a/clippy_lints/src/manual_slice_size_calculation.rs +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -40,11 +40,11 @@ declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION] impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - // Does not apply inside const because size_of_val is not cost in stable. - if !in_constant(cx, expr.hir_id) - && let ExprKind::Binary(ref op, left, right) = expr.kind + if let ExprKind::Binary(ref op, left, right) = expr.kind && BinOpKind::Mul == op.node && !expr.span.from_expansion() + // Does not apply inside const because size_of_val is not cost in stable. + && !in_constant(cx, expr.hir_id) && let Some(receiver) = simplify(cx, left, right) { let ctxt = expr.span.ctxt(); From 5d333e862b5a2df69333952c331cdb06b4fd12e4 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 18:26:38 -0400 Subject: [PATCH 094/107] `manual_strip`: Delay msrv check. --- clippy_lints/src/manual_strip.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 45af9f07718d2..6a523ad156472 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -66,14 +66,11 @@ enum StripKind { impl<'tcx> LateLintPass<'tcx> for ManualStrip { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !self.msrv.meets(msrvs::STR_STRIP_PREFIX) { - return; - } - if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) && let ExprKind::MethodCall(_, target_arg, [pattern], _) = cond.kind - && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) && let ExprKind::Path(target_path) = &target_arg.kind + && self.msrv.meets(msrvs::STR_STRIP_PREFIX) + && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(cond.hir_id) { let strip_kind = if match_def_path(cx, method_def_id, &paths::STR_STARTS_WITH) { StripKind::Prefix From 7a942a5c993233700a217dae629915bba456d698 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 18:34:25 -0400 Subject: [PATCH 095/107] `manual_unwrap_or_default`: Check HIR tree first. --- clippy_lints/src/manual_unwrap_or_default.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/manual_unwrap_or_default.rs b/clippy_lints/src/manual_unwrap_or_default.rs index 58b2ebebbf080..f1acc4b217480 100644 --- a/clippy_lints/src/manual_unwrap_or_default.rs +++ b/clippy_lints/src/manual_unwrap_or_default.rs @@ -172,11 +172,10 @@ fn handle<'tcx>(cx: &LateContext<'tcx>, if_let_or_match: IfLetOrMatch<'tcx>, exp impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOrDefault { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { - if expr.span.from_expansion() || in_constant(cx, expr.hir_id) { - return; - } - // Call handle only if the expression is `if let` or `match` - if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr) { + if let Some(if_let_or_match) = IfLetOrMatch::parse(cx, expr) + && !expr.span.from_expansion() + && !in_constant(cx, expr.hir_id) + { handle(cx, if_let_or_match, expr); } } From 488a545a50a8b1300f1a543f1de0238905352b57 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 18:35:47 -0400 Subject: [PATCH 096/107] `map_unit_fn`: Delay macro check --- clippy_lints/src/map_unit_fn.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index 9db04b615be7b..2db71b1f7a388 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -253,14 +253,11 @@ fn lint_map_unit_fn( impl<'tcx> LateLintPass<'tcx> for MapUnit { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { - if stmt.span.from_expansion() { - return; - } - - if let hir::StmtKind::Semi(expr) = stmt.kind { - if let Some(arglists) = method_chain_args(expr, &["map"]) { - lint_map_unit_fn(cx, stmt, expr, arglists[0]); - } + if let hir::StmtKind::Semi(expr) = stmt.kind + && !stmt.span.from_expansion() + && let Some(arglists) = method_chain_args(expr, &["map"]) + { + lint_map_unit_fn(cx, stmt, expr, arglists[0]); } } } From eda45aaba1124907b9551589cd5e9b97f1972313 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 13 Jun 2024 18:46:45 -0400 Subject: [PATCH 097/107] `minmax`: use let chain --- clippy_lints/src/minmax.rs | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index fca626fa5c35c..c3fbca1d560e6 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -5,7 +5,7 @@ use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::sym; -use std::cmp::Ordering; +use std::cmp::Ordering::{Equal, Greater, Less}; declare_clippy_lint! { /// ### What it does @@ -36,26 +36,21 @@ declare_lint_pass!(MinMaxPass => [MIN_MAX]); impl<'tcx> LateLintPass<'tcx> for MinMaxPass { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) { - if let Some((inner_max, inner_c, ie)) = min_max(cx, oe) { - if outer_max == inner_max { - return; - } - match ( - outer_max, - Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c), - ) { - (_, None) | (MinMax::Max, Some(Ordering::Less)) | (MinMax::Min, Some(Ordering::Greater)) => (), - _ => { - span_lint( - cx, - MIN_MAX, - expr.span, - "this `min`/`max` combination leads to constant result", - ); - }, - } - } + if let Some((outer_max, outer_c, oe)) = min_max(cx, expr) + && let Some((inner_max, inner_c, ie)) = min_max(cx, oe) + && outer_max != inner_max + && let Some(ord) = Constant::partial_cmp(cx.tcx, cx.typeck_results().expr_ty(ie), &outer_c, &inner_c) + && matches!( + (outer_max, ord), + (MinMax::Max, Equal | Greater) | (MinMax::Min, Equal | Less) + ) + { + span_lint( + cx, + MIN_MAX, + expr.span, + "this `min`/`max` combination leads to constant result", + ); } } } From 5b7ffa1a63a4553e2946d70068629a6e7c9b7fdd Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 Jun 2024 13:21:09 -0400 Subject: [PATCH 098/107] Rework `init_numbered_fields`: * Only check the name of a single field * Don't lint in macros * Check for side effects * Don't use a binary heap --- clippy_lints/src/init_numbered_fields.rs | 87 +++++++++++-------- clippy_lints/src/lib.rs | 1 + ...ields.fixed => init_numbered_fields.fixed} | 9 ++ ...ered_fields.rs => init_numbered_fields.rs} | 9 ++ ...lds.stderr => init_numbered_fields.stderr} | 12 +-- 5 files changed, 77 insertions(+), 41 deletions(-) rename tests/ui/{numbered_fields.fixed => init_numbered_fields.fixed} (82%) rename tests/ui/{numbered_fields.rs => init_numbered_fields.rs} (84%) rename tests/ui/{numbered_fields.stderr => init_numbered_fields.stderr} (63%) diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index 1c8fd0a27f98f..7f183bb601eb4 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -1,13 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::SyntaxContext; use std::borrow::Cow; -use std::cmp::Reverse; -use std::collections::BinaryHeap; declare_clippy_lint! { /// ### What it does @@ -44,38 +43,56 @@ declare_lint_pass!(NumberedFields => [INIT_NUMBERED_FIELDS]); impl<'tcx> LateLintPass<'tcx> for NumberedFields { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { - if let ExprKind::Struct(path, fields, None) = e.kind { - if !fields.is_empty() - && !e.span.from_expansion() - && fields - .iter() - .all(|f| f.ident.as_str().as_bytes().iter().all(u8::is_ascii_digit)) - && !matches!(cx.qpath_res(path, e.hir_id), Res::Def(DefKind::TyAlias, ..)) - { - let expr_spans = fields - .iter() - .map(|f| (Reverse(f.ident.as_str().parse::().unwrap()), f.expr.span)) - .collect::>(); - let mut appl = Applicability::MachineApplicable; - let snippet = format!( - "{}({})", - snippet_with_applicability(cx, path.span(), "..", &mut appl), - expr_spans - .into_iter_sorted() - .map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) - .intersperse(Cow::Borrowed(", ")) - .collect::() - ); - span_lint_and_sugg( - cx, - INIT_NUMBERED_FIELDS, - e.span, - "used a field initializer for a tuple struct", - "try", - snippet, - appl, - ); - } + if let ExprKind::Struct(path, fields @ [field, ..], None) = e.kind + // If the first character of any field is a digit it has to be a tuple. + && field.ident.as_str().as_bytes().first().is_some_and(u8::is_ascii_digit) + // Type aliases can't be used as functions. + && !matches!( + cx.qpath_res(path, e.hir_id), + Res::Def(DefKind::TyAlias | DefKind::AssocTy, _) + ) + // This is the only syntax macros can use that works for all struct types. + && !e.span.from_expansion() + && let mut has_side_effects = false + && let Ok(mut expr_spans) = fields + .iter() + .map(|f| { + has_side_effects |= f.expr.can_have_side_effects(); + f.ident.as_str().parse::().map(|x| (x, f.expr.span)) + }) + .collect::, _>>() + // We can only reorder the expressions if there are no side effects. + && (!has_side_effects || expr_spans.is_sorted_by_key(|&(idx, _)| idx)) + { + span_lint_and_then( + cx, + INIT_NUMBERED_FIELDS, + e.span, + "used a field initializer for a tuple struct", + |diag| { + if !has_side_effects { + // We already checked the order if there are side effects. + expr_spans.sort_by_key(|&(idx, _)| idx); + } + let mut app = Applicability::MachineApplicable; + diag.span_suggestion( + e.span, + "use tuple initialization", + format!( + "{}({})", + snippet_with_applicability(cx, path.span(), "..", &mut app), + expr_spans + .into_iter() + .map( + |(_, span)| snippet_with_context(cx, span, SyntaxContext::root(), "..", &mut app).0 + ) + .intersperse(Cow::Borrowed(", ")) + .collect::() + ), + app, + ); + }, + ); } } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fbe895fa50e82..54e19c1aa7f9c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -4,6 +4,7 @@ #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] +#![feature(is_sorted)] #![feature(iter_intersperse)] #![feature(let_chains)] #![feature(never_type)] diff --git a/tests/ui/numbered_fields.fixed b/tests/ui/init_numbered_fields.fixed similarity index 82% rename from tests/ui/numbered_fields.fixed rename to tests/ui/init_numbered_fields.fixed index 108520eed38d5..dca4e8da4d2f7 100644 --- a/tests/ui/numbered_fields.fixed +++ b/tests/ui/init_numbered_fields.fixed @@ -39,4 +39,13 @@ fn main() { struct TupleStructVec(Vec); let _ = TupleStructVec(vec![0, 1, 2, 3]); + + { + struct S(i32, i32); + let mut iter = [1i32, 1i32].into_iter(); + let _ = S { + 1: iter.next().unwrap(), + 0: iter.next().unwrap(), + }; + } } diff --git a/tests/ui/numbered_fields.rs b/tests/ui/init_numbered_fields.rs similarity index 84% rename from tests/ui/numbered_fields.rs rename to tests/ui/init_numbered_fields.rs index c718661a68263..8cb34705b4f67 100644 --- a/tests/ui/numbered_fields.rs +++ b/tests/ui/init_numbered_fields.rs @@ -47,4 +47,13 @@ fn main() { struct TupleStructVec(Vec); let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; + + { + struct S(i32, i32); + let mut iter = [1i32, 1i32].into_iter(); + let _ = S { + 1: iter.next().unwrap(), + 0: iter.next().unwrap(), + }; + } } diff --git a/tests/ui/numbered_fields.stderr b/tests/ui/init_numbered_fields.stderr similarity index 63% rename from tests/ui/numbered_fields.stderr rename to tests/ui/init_numbered_fields.stderr index 9d3f59cd3769e..f176e0c2ff301 100644 --- a/tests/ui/numbered_fields.stderr +++ b/tests/ui/init_numbered_fields.stderr @@ -1,5 +1,5 @@ error: used a field initializer for a tuple struct - --> tests/ui/numbered_fields.rs:17:13 + --> tests/ui/init_numbered_fields.rs:17:13 | LL | let _ = TupleStruct { | _____________^ @@ -7,13 +7,13 @@ LL | | 0: 1u32, LL | | 1: 42, LL | | 2: 23u8, LL | | }; - | |_____^ help: try: `TupleStruct(1u32, 42, 23u8)` + | |_____^ help: use tuple initialization: `TupleStruct(1u32, 42, 23u8)` | = note: `-D clippy::init-numbered-fields` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::init_numbered_fields)]` error: used a field initializer for a tuple struct - --> tests/ui/numbered_fields.rs:24:13 + --> tests/ui/init_numbered_fields.rs:24:13 | LL | let _ = TupleStruct { | _____________^ @@ -21,13 +21,13 @@ LL | | 0: 1u32, LL | | 2: 2u8, LL | | 1: 3u32, LL | | }; - | |_____^ help: try: `TupleStruct(1u32, 3u32, 2u8)` + | |_____^ help: use tuple initialization: `TupleStruct(1u32, 3u32, 2u8)` error: used a field initializer for a tuple struct - --> tests/ui/numbered_fields.rs:49:13 + --> tests/ui/init_numbered_fields.rs:49:13 | LL | let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `TupleStructVec(vec![0, 1, 2, 3])` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use tuple initialization: `TupleStructVec(vec![0, 1, 2, 3])` error: aborting due to 3 previous errors From 08992d03728015e7305d496540a494c4325196fa Mon Sep 17 00:00:00 2001 From: J-ZhengLi Date: Mon, 8 Jul 2024 18:09:49 +0800 Subject: [PATCH 099/107] [`missing_const_for_fn`]: fix FP when arg ty is impl trait alias ty --- clippy_lints/src/missing_const_for_fn.rs | 17 +++++++++++++++++ tests/ui/missing_const_for_fn/cant_be_const.rs | 9 +++++++++ .../missing_const_for_fn/could_be_const.fixed | 15 +++++++++++++++ tests/ui/missing_const_for_fn/could_be_const.rs | 15 +++++++++++++++ .../missing_const_for_fn/could_be_const.stderr | 13 ++++++++++++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/missing_const_for_fn.rs b/clippy_lints/src/missing_const_for_fn.rs index 44ec1bd08cf33..ed5f46ddc8d20 100644 --- a/clippy_lints/src/missing_const_for_fn.rs +++ b/clippy_lints/src/missing_const_for_fn.rs @@ -8,6 +8,7 @@ use rustc_hir::intravisit::FnKind; use rustc_hir::{self as hir, Body, Constness, FnDecl, GenericParamKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; +use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::Span; @@ -131,6 +132,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingConstForFn { FnKind::Closure => return, } + if fn_inputs_has_impl_trait_ty(cx, def_id) { + return; + } + let hir_id = cx.tcx.local_def_id_to_hir_id(def_id); // Const fns are not allowed as methods in a trait. @@ -185,3 +190,15 @@ fn could_be_const_with_abi(cx: &LateContext<'_>, msrv: &Msrv, abi: Abi) -> bool _ => cx.tcx.features().const_extern_fn, } } + +/// Return `true` when the given `def_id` is a function that has `impl Trait` ty as one of +/// its parameter types. +fn fn_inputs_has_impl_trait_ty(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { + let inputs = cx.tcx.fn_sig(def_id).instantiate_identity().inputs().skip_binder(); + inputs.iter().any(|input| { + matches!( + input.kind(), + ty::Alias(ty::AliasTyKind::Weak, alias_ty) if cx.tcx.type_of(alias_ty.def_id).skip_binder().is_impl_trait() + ) + }) +} diff --git a/tests/ui/missing_const_for_fn/cant_be_const.rs b/tests/ui/missing_const_for_fn/cant_be_const.rs index 952ff8283632e..2c6e1e92da025 100644 --- a/tests/ui/missing_const_for_fn/cant_be_const.rs +++ b/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -7,6 +7,7 @@ #![warn(clippy::missing_const_for_fn)] #![feature(start)] +#![feature(type_alias_impl_trait)] extern crate helper; extern crate proc_macros; @@ -190,3 +191,11 @@ mod with_extern { extern "system" fn system() {} extern "system-unwind" fn system_unwind() {} } + +mod with_ty_alias { + type Foo = impl std::fmt::Debug; + + fn foo(_: Foo) { + let _: Foo = 1; + } +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 572ca52439a6b..2311bd0acda9c 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -186,3 +186,18 @@ mod issue12677 { } } } + +mod with_ty_alias { + trait FooTrait { + type Foo: std::fmt::Debug; + fn bar(_: Self::Foo) {} + } + impl FooTrait for () { + type Foo = i32; + } + // NOTE: When checking the type of a function param, make sure it is not an alias with + // `AliasTyKind::Projection` before calling `TyCtxt::type_of` to find out what the actual type + // is. Because the associate ty could have no default, therefore would cause ICE, as demostrated + // in this test. + const fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 4d19c4d7a48e4..cb7733ed382ac 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -186,3 +186,18 @@ mod issue12677 { } } } + +mod with_ty_alias { + trait FooTrait { + type Foo: std::fmt::Debug; + fn bar(_: Self::Foo) {} + } + impl FooTrait for () { + type Foo = i32; + } + // NOTE: When checking the type of a function param, make sure it is not an alias with + // `AliasTyKind::Projection` before calling `TyCtxt::type_of` to find out what the actual type + // is. Because the associate ty could have no default, therefore would cause ICE, as demostrated + // in this test. + fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} +} diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index ab36e0a281a7a..3cae5bcbc4ab0 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -261,5 +261,16 @@ help: make the function `const` LL | pub const fn new(text: String) -> Self { | +++++ -error: aborting due to 19 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:202:5 + | +LL | fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: make the function `const` + | +LL | const fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} + | +++++ + +error: aborting due to 20 previous errors From d23df7471298610f7afc68635fbc005e07a16f6b Mon Sep 17 00:00:00 2001 From: John Arundel Date: Mon, 8 Jul 2024 19:05:35 +0100 Subject: [PATCH 100/107] resolve code review comments --- clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/casts/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 1a4dc38e7e9c9..d4a1e2780d083 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -23,7 +23,7 @@ declare_clippy_lint! { /// type. Many asynchronous foundation crates provide such a `Mutex` type. /// The other solution is to ensure the mutex is unlocked before calling /// `await`, either by introducing a scope or an explicit call to - /// [`Drop::drop`]. + /// [`Drop::drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html). /// /// ### Known problems /// Will report false positive for explicitly dropped guards diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 14f9f991d3be2..54f0c7c468719 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -219,7 +219,7 @@ declare_clippy_lint! { /// Dereferencing the resulting pointer may be undefined behavior. /// /// ### Known problems - /// Using [`std::ptr::read_unaligned`] and [`std::ptr::write_unaligned`] or + /// Using [`std::ptr::read_unaligned`](https://doc.rust-lang.org/std/ptr/fn.read_unaligned.html) and [`std::ptr::write_unaligned`](https://doc.rust-lang.org/std/ptr/fn.write_unaligned.html) or /// similar on the resulting pointer is fine. Is over-zealous: casts with /// manual alignment checks or casts like `u64` -> `u8` -> `u16` can be /// fine. Miri is able to do a more in-depth analysis. From 920cbcdd456504acb6999fc647490fed0fe6eece Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 8 Jul 2024 15:36:57 -0400 Subject: [PATCH 101/107] Move trait selection error reporting to its own top-level module --- clippy_lints/src/eta_reduction.rs | 2 +- clippy_lints/src/future_not_send.rs | 2 +- clippy_lints/src/unconditional_recursion.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 42ec2c00823c8..d2a34c7558321 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::{ use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; use rustc_target::spec::abi::Abi; -use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _; +use rustc_trait_selection::error_reporting::traits::InferCtxtExt as _; declare_clippy_lint! { /// ### What it does diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index cb1d0de1edff9..1fd8faf3ea883 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -9,7 +9,7 @@ use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind}; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; use rustc_span::{sym, Span}; -use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt; +use rustc_trait_selection::error_reporting::traits::suggestions::TypeErrCtxtExt; use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCtxt}; declare_clippy_lint! { diff --git a/clippy_lints/src/unconditional_recursion.rs b/clippy_lints/src/unconditional_recursion.rs index 5e41b3f4914ff..6ba98a92423c7 100644 --- a/clippy_lints/src/unconditional_recursion.rs +++ b/clippy_lints/src/unconditional_recursion.rs @@ -15,7 +15,7 @@ use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::symbol::{kw, Ident}; use rustc_span::{sym, Span}; -use rustc_trait_selection::traits::error_reporting::suggestions::ReturnsVisitor; +use rustc_trait_selection::error_reporting::traits::suggestions::ReturnsVisitor; declare_clippy_lint! { /// ### What it does From a067cd24ace399be139578aeb0b3137785a79eb7 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Tue, 9 Jul 2024 10:12:09 +0300 Subject: [PATCH 102/107] Replace incorrect suggested fix for `float_cmp` Using `f32::EPSILON` or `f64::EPSILON` as the floating-point equality comparison error margin is incorrect, yet `float_cmp` has until now recommended this be done. This change fixes the given guidance (both in docs and compiler hints) to not reference these unsuitable constants. Instead, the guidance now clarifies that the scenarios in which an absolute error margin is usable, provides a reference implementation of using a user-defined absolute error margin (as an absolute error margin can only be used-defined and may be different for different comparisons) and references the floating point guide for a reference implementation of relative error based equaltiy comparison for when absolute error margin cannot be used. changelog: Fix guidance of [`float_cmp`] and [`float_cmp_const`] to not incorrectly recommend `f64::EPSILON` as the error margin. Fixes #6816 --- clippy_lints/src/operators/float_cmp.rs | 1 - clippy_lints/src/operators/mod.rs | 114 ++++++++++++++++-------- tests/ui/float_cmp.rs | 6 -- tests/ui/float_cmp.stderr | 21 ++--- tests/ui/float_cmp_const.rs | 8 -- tests/ui/float_cmp_const.stderr | 29 ++---- 6 files changed, 91 insertions(+), 88 deletions(-) diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index faab79de9d3cc..0e5b440c50f27 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -57,7 +57,6 @@ pub(crate) fn check<'tcx>( Applicability::HasPlaceholders, // snippet ); } - diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`"); }); } } diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 1079934934c5d..32323f657b0e3 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -574,69 +574,113 @@ declare_clippy_lint! { /// implement equality for a type involving floats). /// /// ### Why is this bad? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). + /// Floating point calculations are usually imprecise, so asking if two values are *exactly* + /// equal is asking for trouble because arriving at the same logical result via different + /// routes (e.g. calculation versus constant) may yield different values. /// /// ### Example + /// /// ```no_run - /// let x = 1.2331f64; - /// let y = 1.2332f64; + /// let a: f64 = 1000.1; + /// let b: f64 = 0.2; + /// let x = a + b; + /// let y = 1000.3; // Expected value. + /// + /// // Actual value: 1000.3000000000001 + /// println!("{x}"); /// - /// if y == 1.23f64 { } - /// if y != x {} // where both are floats + /// let are_equal = x == y; + /// println!("{are_equal}"); // false /// ``` /// - /// Use instead: + /// The correct way to compare floating point numbers is to define an allowed error margin. This + /// may be challenging if there is no "natural" error margin to permit. Broadly speaking, there + /// are two cases: + /// + /// 1. If your values are in a known range and you can define a threshold for "close enough to + /// be equal", it may be appropriate to define an absolute error margin. For example, if your + /// data is "length of vehicle in centimeters", you may consider 0.1 cm to be "close enough". + /// 1. If your code is more general and you do not know the range of values, you should use a + /// relative error margin, accepting e.g. 0.1% of error regardless of specific values. + /// + /// For the scenario where you can define a meaningful absolute error margin, consider using: + /// /// ```no_run - /// # let x = 1.2331f64; - /// # let y = 1.2332f64; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (y - 1.23f64).abs() < error_margin { } - /// if (y - x).abs() > error_margin { } + /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1; + /// let within_tolerance = (x - y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM; + /// println!("{within_tolerance}"); // true /// ``` + /// + /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// a different use of the term that is not suitable for floating point equality comparison. + /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. + /// + /// For the scenario where no meaningful absolute error can be defined, refer to + /// [the floating point guide](https://www.floating-point-gui.de/errors/comparison) + /// for a reference implementation of relative error based comparison of floating point values. + /// `MIN_NORMAL` in the reference implementation is equivalent to `MIN_POSITIVE` in Rust. #[clippy::version = "pre 1.29.0"] pub FLOAT_CMP, pedantic, - "using `==` or `!=` on float values instead of comparing difference with an epsilon" + "using `==` or `!=` on float values instead of comparing difference with an allowed error" } declare_clippy_lint! { /// ### What it does - /// Checks for (in-)equality comparisons on floating-point - /// value and constant, except in functions called `*eq*` (which probably + /// Checks for (in-)equality comparisons on constant floating-point + /// values (apart from zero), except in functions called `*eq*` (which probably /// implement equality for a type involving floats). /// - /// ### Why restrict this? - /// Floating point calculations are usually imprecise, so - /// asking if two values are *exactly* equal is asking for trouble. For a good - /// guide on what to do, see [the floating point - /// guide](http://www.floating-point-gui.de/errors/comparison). + /// ### Why is this bad? + /// Floating point calculations are usually imprecise, so asking if two values are *exactly* + /// equal is asking for trouble because arriving at the same logical result via different + /// routes (e.g. calculation versus constant) may yield different values. /// /// ### Example + /// /// ```no_run - /// let x: f64 = 1.0; - /// const ONE: f64 = 1.00; + /// let a: f64 = 1000.1; + /// let b: f64 = 0.2; + /// let x = a + b; + /// const Y: f64 = 1000.3; // Expected value. /// - /// if x == ONE { } // where both are floats + /// // Actual value: 1000.3000000000001 + /// println!("{x}"); + /// + /// let are_equal = x == y; + /// println!("{are_equal}"); // false /// ``` /// - /// Use instead: + /// The correct way to compare floating point numbers is to define an allowed error margin. This + /// may be challenging if there is no "natural" error margin to permit. Broadly speaking, there + /// are two cases: + /// + /// 1. If your values are in a known range and you can define a threshold for "close enough to + /// be equal", it may be appropriate to define an absolute error margin. For example, if your + /// data is "length of vehicle in centimeters", you may consider 0.1 cm to be "close enough". + /// 1. If your code is more general and you do not know the range of values, you should use a + /// relative error margin, accepting e.g. 0.1% of error regardless of specific values. + /// + /// For the scenario where you can define a meaningful absolute error margin, consider using: + /// /// ```no_run - /// # let x: f64 = 1.0; - /// # const ONE: f64 = 1.00; - /// let error_margin = f64::EPSILON; // Use an epsilon for comparison - /// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead. - /// // let error_margin = std::f64::EPSILON; - /// if (x - ONE).abs() < error_margin { } + /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1; + /// let within_tolerance = (x - Y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM; + /// println!("{within_tolerance}"); // true /// ``` + /// + /// NB! Do not use `f64::EPSILON` - while the error margin is often called "epsilon", this is + /// a different use of the term that is not suitable for floating point equality comparison. + /// Indeed, for the example above using `f64::EPSILON` as the allowed error would return `false`. + /// + /// For the scenario where no meaningful absolute error can be defined, refer to + /// [the floating point guide](https://www.floating-point-gui.de/errors/comparison) + /// for a reference implementation of relative error based comparison of floating point values. + /// `MIN_NORMAL` in the reference implementation is equivalent to `MIN_POSITIVE` in Rust. #[clippy::version = "pre 1.29.0"] pub FLOAT_CMP_CONST, restriction, - "using `==` or `!=` on float constants instead of comparing difference with an epsilon" + "using `==` or `!=` on float constants instead of comparing difference with an allowed error" } declare_clippy_lint! { diff --git a/tests/ui/float_cmp.rs b/tests/ui/float_cmp.rs index 1923ad7c677e0..78dd2c6c01c3d 100644 --- a/tests/ui/float_cmp.rs +++ b/tests/ui/float_cmp.rs @@ -71,19 +71,16 @@ fn main() { twice(ONE) != ONE; ONE as f64 != 2.0; //~^ ERROR: strict comparison of `f32` or `f64` - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` ONE as f64 != 0.0; // no error, comparison with zero is ok let x: f64 = 1.0; x == 1.0; //~^ ERROR: strict comparison of `f32` or `f64` - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` x != 0f64; // no error, comparison with zero is ok twice(x) != twice(ONE as f64); //~^ ERROR: strict comparison of `f32` or `f64` - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` x < 0.0; // no errors, lower or greater comparisons need no fuzzyness x > 0.0; @@ -105,17 +102,14 @@ fn main() { ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; // ok, because lhs is zero regardless of i NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; //~^ ERROR: strict comparison of `f32` or `f64` - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` let a1: [f32; 1] = [0.0]; let a2: [f32; 1] = [1.1]; a1 == a2; //~^ ERROR: strict comparison of `f32` or `f64` arrays - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` a1[0] == a2[0]; //~^ ERROR: strict comparison of `f32` or `f64` - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` // no errors - comparing signums is ok let x32 = 3.21f32; diff --git a/tests/ui/float_cmp.stderr b/tests/ui/float_cmp.stderr index c8a0bde6e63a8..d10da8a99a9d8 100644 --- a/tests/ui/float_cmp.stderr +++ b/tests/ui/float_cmp.stderr @@ -4,49 +4,38 @@ error: strict comparison of `f32` or `f64` LL | ONE as f64 != 2.0; | ^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(ONE as f64 - 2.0).abs() > error_margin` | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` = note: `-D clippy::float-cmp` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::float_cmp)]` error: strict comparison of `f32` or `f64` - --> tests/ui/float_cmp.rs:79:5 + --> tests/ui/float_cmp.rs:78:5 | LL | x == 1.0; | ^^^^^^^^ help: consider comparing them within some margin of error: `(x - 1.0).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` - --> tests/ui/float_cmp.rs:84:5 + --> tests/ui/float_cmp.rs:82:5 | LL | twice(x) != twice(ONE as f64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(twice(x) - twice(ONE as f64)).abs() > error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` - --> tests/ui/float_cmp.rs:106:5 + --> tests/ui/float_cmp.rs:103:5 | LL | NON_ZERO_ARRAY[i] == NON_ZERO_ARRAY[j]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(NON_ZERO_ARRAY[i] - NON_ZERO_ARRAY[j]).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` arrays - --> tests/ui/float_cmp.rs:113:5 + --> tests/ui/float_cmp.rs:109:5 | LL | a1 == a2; | ^^^^^^^^ - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` - --> tests/ui/float_cmp.rs:116:5 + --> tests/ui/float_cmp.rs:111:5 | LL | a1[0] == a2[0]; | ^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(a1[0] - a2[0]).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: aborting due to 6 previous errors diff --git a/tests/ui/float_cmp_const.rs b/tests/ui/float_cmp_const.rs index 47ea0e19c68b9..0818055643734 100644 --- a/tests/ui/float_cmp_const.rs +++ b/tests/ui/float_cmp_const.rs @@ -15,28 +15,21 @@ fn main() { // has errors 1f32 == ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` TWO == ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` TWO != ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` ONE + ONE == TWO; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` let x = 1; x as f32 == ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` let v = 0.9; v == ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` v != ONE; //~^ ERROR: strict comparison of `f32` or `f64` constant - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` // no errors, lower than or greater than comparisons v < ONE; @@ -70,5 +63,4 @@ fn main() { // has errors NON_ZERO_ARRAY == NON_ZERO_ARRAY2; //~^ ERROR: strict comparison of `f32` or `f64` constant arrays - //~| NOTE: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` } diff --git a/tests/ui/float_cmp_const.stderr b/tests/ui/float_cmp_const.stderr index bffd2acc2e049..4f88746e958e2 100644 --- a/tests/ui/float_cmp_const.stderr +++ b/tests/ui/float_cmp_const.stderr @@ -4,65 +4,50 @@ error: strict comparison of `f32` or `f64` constant LL | 1f32 == ONE; | ^^^^^^^^^^^ help: consider comparing them within some margin of error: `(1f32 - ONE).abs() < error_margin` | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` = note: `-D clippy::float-cmp-const` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::float_cmp_const)]` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:19:5 + --> tests/ui/float_cmp_const.rs:18:5 | LL | TWO == ONE; | ^^^^^^^^^^ help: consider comparing them within some margin of error: `(TWO - ONE).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:22:5 + --> tests/ui/float_cmp_const.rs:20:5 | LL | TWO != ONE; | ^^^^^^^^^^ help: consider comparing them within some margin of error: `(TWO - ONE).abs() > error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:25:5 + --> tests/ui/float_cmp_const.rs:22:5 | LL | ONE + ONE == TWO; | ^^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(ONE + ONE - TWO).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:29:5 + --> tests/ui/float_cmp_const.rs:25:5 | LL | x as f32 == ONE; | ^^^^^^^^^^^^^^^ help: consider comparing them within some margin of error: `(x as f32 - ONE).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:34:5 + --> tests/ui/float_cmp_const.rs:29:5 | LL | v == ONE; | ^^^^^^^^ help: consider comparing them within some margin of error: `(v - ONE).abs() < error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant - --> tests/ui/float_cmp_const.rs:37:5 + --> tests/ui/float_cmp_const.rs:31:5 | LL | v != ONE; | ^^^^^^^^ help: consider comparing them within some margin of error: `(v - ONE).abs() > error_margin` - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: strict comparison of `f32` or `f64` constant arrays - --> tests/ui/float_cmp_const.rs:71:5 + --> tests/ui/float_cmp_const.rs:64:5 | LL | NON_ZERO_ARRAY == NON_ZERO_ARRAY2; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: `f32::EPSILON` and `f64::EPSILON` are available for the `error_margin` error: aborting due to 8 previous errors From a04dbb0ab9fd79cba5faaa2cf8e03082b2831387 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Tue, 9 Jul 2024 10:28:11 +0300 Subject: [PATCH 103/107] Tidy the example code and ensure it builds as standalone snippets --- clippy_lints/src/operators/mod.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 32323f657b0e3..bf7a581c84863 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -606,6 +606,11 @@ declare_clippy_lint! { /// For the scenario where you can define a meaningful absolute error margin, consider using: /// /// ```no_run + /// let a: f64 = 1000.1; + /// let b: f64 = 0.2; + /// let x = a + b; + /// let y = 1000.3; // Expected value. + /// /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1; /// let within_tolerance = (x - y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM; /// println!("{within_tolerance}"); // true @@ -647,7 +652,7 @@ declare_clippy_lint! { /// // Actual value: 1000.3000000000001 /// println!("{x}"); /// - /// let are_equal = x == y; + /// let are_equal = x == Y; /// println!("{are_equal}"); // false /// ``` /// @@ -664,6 +669,11 @@ declare_clippy_lint! { /// For the scenario where you can define a meaningful absolute error margin, consider using: /// /// ```no_run + /// let a: f64 = 1000.1; + /// let b: f64 = 0.2; + /// let x = a + b; + /// const Y: f64 = 1000.3; // Expected value. + /// /// const ALLOWED_ERROR_VEHICLE_LENGTH_CM: f64 = 0.1; /// let within_tolerance = (x - Y).abs() < ALLOWED_ERROR_VEHICLE_LENGTH_CM; /// println!("{within_tolerance}"); // true From c3a240608b6600c8a5ccc89cdcb823836bea0530 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 8 Jul 2024 16:39:35 +0200 Subject: [PATCH 104/107] Lintcheck: Refactor structs and only take one version per crate --- lintcheck/lintcheck_crates.toml | 52 ++-- lintcheck/src/input.rs | 288 +++++++++++++++++ lintcheck/src/main.rs | 528 +------------------------------- lintcheck/src/output.rs | 235 ++++++++++++++ lintcheck/src/popular_crates.rs | 2 +- lintcheck/src/recursive.rs | 3 +- 6 files changed, 567 insertions(+), 541 deletions(-) create mode 100644 lintcheck/src/input.rs create mode 100644 lintcheck/src/output.rs diff --git a/lintcheck/lintcheck_crates.toml b/lintcheck/lintcheck_crates.toml index 52f7fee47b616..ff608e6f93592 100644 --- a/lintcheck/lintcheck_crates.toml +++ b/lintcheck/lintcheck_crates.toml @@ -1,38 +1,38 @@ [crates] # some of these are from cargotest -cargo = {name = "cargo", versions = ['0.64.0']} -iron = {name = "iron", versions = ['0.6.1']} -ripgrep = {name = "ripgrep", versions = ['12.1.1']} -xsv = {name = "xsv", versions = ['0.13.0']} +cargo = {name = "cargo", version = '0.64.0'} +iron = {name = "iron", version = '0.6.1'} +ripgrep = {name = "ripgrep", version = '12.1.1'} +xsv = {name = "xsv", version = '0.13.0'} # commented out because of 173K clippy::match_same_arms msgs in language_type.rs -#tokei = { name = "tokei", versions = ['12.0.4']} -rayon = {name = "rayon", versions = ['1.5.0']} -serde = {name = "serde", versions = ['1.0.118']} +#tokei = { name = "tokei", version = '12.0.4'} +rayon = {name = "rayon", version = '1.5.0'} +serde = {name = "serde", version = '1.0.118'} # top 10 crates.io dls -bitflags = {name = "bitflags", versions = ['1.2.1']} +bitflags = {name = "bitflags", version = '1.2.1'} # crash = {name = "clippy_crash", path = "/tmp/clippy_crash"} -libc = {name = "libc", versions = ['0.2.81']} -log = {name = "log", versions = ['0.4.11']} -proc-macro2 = {name = "proc-macro2", versions = ['1.0.24']} -quote = {name = "quote", versions = ['1.0.7']} -rand = {name = "rand", versions = ['0.7.3']} -rand_core = {name = "rand_core", versions = ['0.6.0']} -regex = {name = "regex", versions = ['1.3.2']} -syn = {name = "syn", versions = ['1.0.54']} -unicode-xid = {name = "unicode-xid", versions = ['0.2.1']} +libc = {name = "libc", version = '0.2.81'} +log = {name = "log", version = '0.4.11'} +proc-macro2 = {name = "proc-macro2", version = '1.0.24'} +quote = {name = "quote", version = '1.0.7'} +rand = {name = "rand", version = '0.7.3'} +rand_core = {name = "rand_core", version = '0.6.0'} +regex = {name = "regex", version = '1.3.2'} +syn = {name = "syn", version = '1.0.54'} +unicode-xid = {name = "unicode-xid", version = '0.2.1'} # some more of dtolnays crates -anyhow = {name = "anyhow", versions = ['1.0.38']} -async-trait = {name = "async-trait", versions = ['0.1.42']} -cxx = {name = "cxx", versions = ['1.0.32']} -ryu = {name = "ryu", versions = ['1.0.5']} -serde_yaml = {name = "serde_yaml", versions = ['0.8.17']} -thiserror = {name = "thiserror", versions = ['1.0.24']} +anyhow = {name = "anyhow", version = '1.0.38'} +async-trait = {name = "async-trait", version = '0.1.42'} +cxx = {name = "cxx", version = '1.0.32'} +ryu = {name = "ryu", version = '1.0.5'} +serde_yaml = {name = "serde_yaml", version = '0.8.17'} +thiserror = {name = "thiserror", version = '1.0.24'} # some embark crates, there are other interesting crates but # unfortunately adding them increases lintcheck runtime drastically -cfg-expr = {name = "cfg-expr", versions = ['0.7.1']} +cfg-expr = {name = "cfg-expr", version = '0.7.1'} puffin = {name = "puffin", git_url = "https://github.com/EmbarkStudios/puffin", git_hash = "02dd4a3"} -rpmalloc = {name = "rpmalloc", versions = ['0.2.0']} -tame-oidc = {name = "tame-oidc", versions = ['0.1.0']} +rpmalloc = {name = "rpmalloc", version = '0.2.0'} +tame-oidc = {name = "tame-oidc", version = '0.1.0'} [recursive] ignore = [ diff --git a/lintcheck/src/input.rs b/lintcheck/src/input.rs new file mode 100644 index 0000000000000..3d034391c280f --- /dev/null +++ b/lintcheck/src/input.rs @@ -0,0 +1,288 @@ +use std::collections::{HashMap, HashSet}; +use std::fs::{self}; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Duration; + +use serde::Deserialize; +use walkdir::{DirEntry, WalkDir}; + +use crate::{Crate, LINTCHECK_DOWNLOADS, LINTCHECK_SOURCES}; + +/// List of sources to check, loaded from a .toml file +#[derive(Debug, Deserialize)] +pub struct SourceList { + crates: HashMap, + #[serde(default)] + recursive: RecursiveOptions, +} + +#[derive(Debug, Deserialize, Default)] +pub struct RecursiveOptions { + pub ignore: HashSet, +} + +/// A crate source stored inside the .toml +/// will be translated into on one of the `CrateSource` variants +#[derive(Debug, Deserialize)] +struct TomlCrate { + name: String, + version: Option, + git_url: Option, + git_hash: Option, + path: Option, + options: Option>, +} + +/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder +/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] +pub enum CrateSource { + CratesIo { + name: String, + version: String, + options: Option>, + }, + Git { + name: String, + url: String, + commit: String, + options: Option>, + }, + Path { + name: String, + path: PathBuf, + options: Option>, + }, +} + +/// Read a `lintcheck_crates.toml` file +pub fn read_crates(toml_path: &Path) -> (Vec, RecursiveOptions) { + let toml_content: String = + fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display())); + let crate_list: SourceList = + toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{e}", toml_path.display())); + // parse the hashmap of the toml file into a list of crates + let tomlcrates: Vec = crate_list.crates.into_values().collect(); + + // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate => + // multiple Cratesources) + let mut crate_sources = Vec::new(); + for tk in tomlcrates { + if let Some(ref path) = tk.path { + crate_sources.push(CrateSource::Path { + name: tk.name.clone(), + path: PathBuf::from(path), + options: tk.options.clone(), + }); + } else if let Some(ref version) = tk.version { + crate_sources.push(CrateSource::CratesIo { + name: tk.name.clone(), + version: version.to_string(), + options: tk.options.clone(), + }); + } else if tk.git_url.is_some() && tk.git_hash.is_some() { + // otherwise, we should have a git source + crate_sources.push(CrateSource::Git { + name: tk.name.clone(), + url: tk.git_url.clone().unwrap(), + commit: tk.git_hash.clone().unwrap(), + options: tk.options.clone(), + }); + } else { + panic!("Invalid crate source: {tk:?}"); + } + + // if we have a version as well as a git data OR only one git data, something is funky + if tk.version.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some()) + || tk.git_hash.is_some() != tk.git_url.is_some() + { + eprintln!("tomlkrate: {tk:?}"); + assert_eq!( + tk.git_hash.is_some(), + tk.git_url.is_some(), + "Error: Encountered TomlCrate with only one of git_hash and git_url!" + ); + assert!( + tk.path.is_none() || (tk.git_hash.is_none() && tk.version.is_none()), + "Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields" + ); + unreachable!("Failed to translate TomlCrate into CrateSource!"); + } + } + // sort the crates + crate_sources.sort(); + + (crate_sources, crate_list.recursive) +} + +impl CrateSource { + /// Makes the sources available on the disk for clippy to check. + /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or + /// copies a local folder + #[expect(clippy::too_many_lines)] + pub fn download_and_extract(&self) -> Crate { + #[allow(clippy::result_large_err)] + fn get(path: &str) -> Result { + const MAX_RETRIES: u8 = 4; + let mut retries = 0; + loop { + match ureq::get(path).call() { + Ok(res) => return Ok(res), + Err(e) if retries >= MAX_RETRIES => return Err(e), + Err(ureq::Error::Transport(e)) => eprintln!("Error: {e}"), + Err(e) => return Err(e), + } + eprintln!("retrying in {retries} seconds..."); + std::thread::sleep(Duration::from_secs(u64::from(retries))); + retries += 1; + } + } + match self { + CrateSource::CratesIo { name, version, options } => { + let extract_dir = PathBuf::from(LINTCHECK_SOURCES); + let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS); + + // url to download the crate from crates.io + let url = format!("https://crates.io/api/v1/crates/{name}/{version}/download"); + println!("Downloading and extracting {name} {version} from {url}"); + create_dirs(&krate_download_dir, &extract_dir); + + let krate_file_path = krate_download_dir.join(format!("{name}-{version}.crate.tar.gz")); + // don't download/extract if we already have done so + if !krate_file_path.is_file() { + // create a file path to download and write the crate data into + let mut krate_dest = fs::File::create(&krate_file_path).unwrap(); + let mut krate_req = get(&url).unwrap().into_reader(); + // copy the crate into the file + io::copy(&mut krate_req, &mut krate_dest).unwrap(); + + // unzip the tarball + let ungz_tar = flate2::read::GzDecoder::new(fs::File::open(&krate_file_path).unwrap()); + // extract the tar archive + let mut archive = tar::Archive::new(ungz_tar); + archive.unpack(&extract_dir).expect("Failed to extract!"); + } + // crate is extracted, return a new Krate object which contains the path to the extracted + // sources that clippy can check + Crate { + version: version.clone(), + name: name.clone(), + path: extract_dir.join(format!("{name}-{version}/")), + options: options.clone(), + } + }, + CrateSource::Git { + name, + url, + commit, + options, + } => { + let repo_path = { + let mut repo_path = PathBuf::from(LINTCHECK_SOURCES); + // add a -git suffix in case we have the same crate from crates.io and a git repo + repo_path.push(format!("{name}-git")); + repo_path + }; + // clone the repo if we have not done so + if !repo_path.is_dir() { + println!("Cloning {url} and checking out {commit}"); + if !Command::new("git") + .arg("clone") + .arg(url) + .arg(&repo_path) + .status() + .expect("Failed to clone git repo!") + .success() + { + eprintln!("Failed to clone {url} into {}", repo_path.display()); + } + } + // check out the commit/branch/whatever + if !Command::new("git") + .args(["-c", "advice.detachedHead=false"]) + .arg("checkout") + .arg(commit) + .current_dir(&repo_path) + .status() + .expect("Failed to check out commit") + .success() + { + eprintln!("Failed to checkout {commit} of repo at {}", repo_path.display()); + } + + Crate { + version: commit.clone(), + name: name.clone(), + path: repo_path, + options: options.clone(), + } + }, + CrateSource::Path { name, path, options } => { + fn is_cache_dir(entry: &DirEntry) -> bool { + fs::read(entry.path().join("CACHEDIR.TAG")) + .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55")) + .unwrap_or(false) + } + + // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file. + // The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory + // as a result of this filter. + let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name); + if dest_crate_root.exists() { + println!("Deleting existing directory at {dest_crate_root:?}"); + fs::remove_dir_all(&dest_crate_root).unwrap(); + } + + println!("Copying {path:?} to {dest_crate_root:?}"); + + for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) { + let entry = entry.unwrap(); + let entry_path = entry.path(); + let relative_entry_path = entry_path.strip_prefix(path).unwrap(); + let dest_path = dest_crate_root.join(relative_entry_path); + let metadata = entry_path.symlink_metadata().unwrap(); + + if metadata.is_dir() { + fs::create_dir(dest_path).unwrap(); + } else if metadata.is_file() { + fs::copy(entry_path, dest_path).unwrap(); + } + } + + Crate { + version: String::from("local"), + name: name.clone(), + path: dest_crate_root, + options: options.clone(), + } + }, + } + } +} + +/// Create necessary directories to run the lintcheck tool. +/// +/// # Panics +/// +/// This function panics if creating one of the dirs fails. +fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) { + fs::create_dir("target/lintcheck/").unwrap_or_else(|err| { + assert_eq!( + err.kind(), + ErrorKind::AlreadyExists, + "cannot create lintcheck target dir" + ); + }); + fs::create_dir(krate_download_dir).unwrap_or_else(|err| { + assert_eq!(err.kind(), ErrorKind::AlreadyExists, "cannot create crate download dir"); + }); + fs::create_dir(extract_dir).unwrap_or_else(|err| { + assert_eq!( + err.kind(), + ErrorKind::AlreadyExists, + "cannot create crate extraction dir" + ); + }); +} diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 26a67beb4427b..e37ffab13ac89 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -13,84 +13,38 @@ unused_lifetimes, unused_qualifications )] -#![allow(clippy::collapsible_else_if, clippy::needless_borrows_for_generic_args)] +#![allow( + clippy::collapsible_else_if, + clippy::needless_borrows_for_generic_args, + clippy::module_name_repetitions +)] mod config; mod driver; +mod input; mod json; +mod output; mod popular_crates; mod recursive; use crate::config::{Commands, LintcheckConfig, OutputFormat}; use crate::recursive::LintcheckServer; -use std::collections::{HashMap, HashSet}; use std::env::consts::EXE_SUFFIX; -use std::fmt::{self, Display, Write as _}; -use std::hash::Hash; -use std::io::{self, ErrorKind}; +use std::io::{self}; use std::path::{Path, PathBuf}; -use std::process::{Command, ExitStatus, Stdio}; +use std::process::{Command, Stdio}; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::time::Duration; -use std::{env, fs, thread}; +use std::{env, fs}; -use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan}; use cargo_metadata::Message; +use input::{read_crates, CrateSource}; +use output::{ClippyCheckOutput, ClippyWarning, RustcIce}; use rayon::prelude::*; -use serde::Deserialize; -use walkdir::{DirEntry, WalkDir}; const LINTCHECK_DOWNLOADS: &str = "target/lintcheck/downloads"; const LINTCHECK_SOURCES: &str = "target/lintcheck/sources"; -/// List of sources to check, loaded from a .toml file -#[derive(Debug, Deserialize)] -struct SourceList { - crates: HashMap, - #[serde(default)] - recursive: RecursiveOptions, -} - -#[derive(Debug, Deserialize, Default)] -struct RecursiveOptions { - ignore: HashSet, -} - -/// A crate source stored inside the .toml -/// will be translated into on one of the `CrateSource` variants -#[derive(Debug, Deserialize)] -struct TomlCrate { - name: String, - versions: Option>, - git_url: Option, - git_hash: Option, - path: Option, - options: Option>, -} - -/// Represents an archive we download from crates.io, or a git repo, or a local repo/folder -/// Once processed (downloaded/extracted/cloned/copied...), this will be translated into a `Crate` -#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Ord, PartialOrd)] -enum CrateSource { - CratesIo { - name: String, - version: String, - options: Option>, - }, - Git { - name: String, - url: String, - commit: String, - options: Option>, - }, - Path { - name: String, - path: PathBuf, - options: Option>, - }, -} - /// Represents the actual source code of a crate that we ran "cargo clippy" on #[derive(Debug)] struct Crate { @@ -101,241 +55,6 @@ struct Crate { options: Option>, } -/// A single emitted output from clippy being executed on a crate. It may either be a -/// `ClippyWarning`, or a `RustcIce` caused by a panic within clippy. A crate may have many -/// `ClippyWarning`s but a maximum of one `RustcIce` (at which point clippy halts execution). -#[derive(Debug)] -enum ClippyCheckOutput { - ClippyWarning(ClippyWarning), - RustcIce(RustcIce), -} - -#[derive(Debug)] -struct RustcIce { - pub crate_name: String, - pub ice_content: String, -} - -impl Display for RustcIce { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}:\n{}\n========================================\n", - self.crate_name, self.ice_content - ) - } -} - -impl RustcIce { - pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option { - if status.code().unwrap_or(0) == 101 - /* ice exit status */ - { - Some(Self { - crate_name: crate_name.to_owned(), - ice_content: stderr.to_owned(), - }) - } else { - None - } - } -} - -/// A single warning that clippy issued while checking a `Crate` -#[derive(Debug)] -struct ClippyWarning { - lint: String, - diag: Diagnostic, -} - -#[allow(unused)] -impl ClippyWarning { - fn new(mut diag: Diagnostic) -> Option { - let lint = diag.code.clone()?.code; - if !(lint.contains("clippy") || diag.message.contains("clippy")) - || diag.message.contains("could not read cargo metadata") - { - return None; - } - - // --recursive bypasses cargo so we have to strip the rendered output ourselves - let rendered = diag.rendered.as_mut().unwrap(); - *rendered = strip_ansi_escapes::strip_str(&rendered); - - Some(Self { lint, diag }) - } - - fn span(&self) -> &DiagnosticSpan { - self.diag.spans.iter().find(|span| span.is_primary).unwrap() - } - - fn to_output(&self, format: OutputFormat) -> String { - let span = self.span(); - let mut file = span.file_name.clone(); - let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end); - match format { - OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint, self.diag.message), - OutputFormat::Markdown => { - if file.starts_with("target") { - file.insert_str(0, "../"); - } - - let mut output = String::from("| "); - write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap(); - write!(output, r#" | `{:<50}` | "{}" |"#, self.lint, self.diag.message).unwrap(); - output.push('\n'); - output - }, - OutputFormat::Json => unreachable!("JSON output is handled via serde"), - } - } -} - -#[allow(clippy::result_large_err)] -fn get(path: &str) -> Result { - const MAX_RETRIES: u8 = 4; - let mut retries = 0; - loop { - match ureq::get(path).call() { - Ok(res) => return Ok(res), - Err(e) if retries >= MAX_RETRIES => return Err(e), - Err(ureq::Error::Transport(e)) => eprintln!("Error: {e}"), - Err(e) => return Err(e), - } - eprintln!("retrying in {retries} seconds..."); - thread::sleep(Duration::from_secs(u64::from(retries))); - retries += 1; - } -} - -impl CrateSource { - /// Makes the sources available on the disk for clippy to check. - /// Clones a git repo and checks out the specified commit or downloads a crate from crates.io or - /// copies a local folder - fn download_and_extract(&self) -> Crate { - match self { - CrateSource::CratesIo { name, version, options } => { - let extract_dir = PathBuf::from(LINTCHECK_SOURCES); - let krate_download_dir = PathBuf::from(LINTCHECK_DOWNLOADS); - - // url to download the crate from crates.io - let url = format!("https://crates.io/api/v1/crates/{name}/{version}/download"); - println!("Downloading and extracting {name} {version} from {url}"); - create_dirs(&krate_download_dir, &extract_dir); - - let krate_file_path = krate_download_dir.join(format!("{name}-{version}.crate.tar.gz")); - // don't download/extract if we already have done so - if !krate_file_path.is_file() { - // create a file path to download and write the crate data into - let mut krate_dest = fs::File::create(&krate_file_path).unwrap(); - let mut krate_req = get(&url).unwrap().into_reader(); - // copy the crate into the file - io::copy(&mut krate_req, &mut krate_dest).unwrap(); - - // unzip the tarball - let ungz_tar = flate2::read::GzDecoder::new(fs::File::open(&krate_file_path).unwrap()); - // extract the tar archive - let mut archive = tar::Archive::new(ungz_tar); - archive.unpack(&extract_dir).expect("Failed to extract!"); - } - // crate is extracted, return a new Krate object which contains the path to the extracted - // sources that clippy can check - Crate { - version: version.clone(), - name: name.clone(), - path: extract_dir.join(format!("{name}-{version}/")), - options: options.clone(), - } - }, - CrateSource::Git { - name, - url, - commit, - options, - } => { - let repo_path = { - let mut repo_path = PathBuf::from(LINTCHECK_SOURCES); - // add a -git suffix in case we have the same crate from crates.io and a git repo - repo_path.push(format!("{name}-git")); - repo_path - }; - // clone the repo if we have not done so - if !repo_path.is_dir() { - println!("Cloning {url} and checking out {commit}"); - if !Command::new("git") - .arg("clone") - .arg(url) - .arg(&repo_path) - .status() - .expect("Failed to clone git repo!") - .success() - { - eprintln!("Failed to clone {url} into {}", repo_path.display()); - } - } - // check out the commit/branch/whatever - if !Command::new("git") - .args(["-c", "advice.detachedHead=false"]) - .arg("checkout") - .arg(commit) - .current_dir(&repo_path) - .status() - .expect("Failed to check out commit") - .success() - { - eprintln!("Failed to checkout {commit} of repo at {}", repo_path.display()); - } - - Crate { - version: commit.clone(), - name: name.clone(), - path: repo_path, - options: options.clone(), - } - }, - CrateSource::Path { name, path, options } => { - fn is_cache_dir(entry: &DirEntry) -> bool { - fs::read(entry.path().join("CACHEDIR.TAG")) - .map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55")) - .unwrap_or(false) - } - - // copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file. - // The target/ directory contains a CACHEDIR.TAG file so it is the most commonly skipped directory - // as a result of this filter. - let dest_crate_root = PathBuf::from(LINTCHECK_SOURCES).join(name); - if dest_crate_root.exists() { - println!("Deleting existing directory at {dest_crate_root:?}"); - fs::remove_dir_all(&dest_crate_root).unwrap(); - } - - println!("Copying {path:?} to {dest_crate_root:?}"); - - for entry in WalkDir::new(path).into_iter().filter_entry(|e| !is_cache_dir(e)) { - let entry = entry.unwrap(); - let entry_path = entry.path(); - let relative_entry_path = entry_path.strip_prefix(path).unwrap(); - let dest_path = dest_crate_root.join(relative_entry_path); - let metadata = entry_path.symlink_metadata().unwrap(); - - if metadata.is_dir() { - fs::create_dir(dest_path).unwrap(); - } else if metadata.is_file() { - fs::copy(entry_path, dest_path).unwrap(); - } - } - - Crate { - version: String::from("local"), - name: name.clone(), - path: dest_crate_root, - options: options.clone(), - } - }, - } - } -} - impl Crate { /// Run `cargo clippy` on the `Crate` and collect and return all the lint warnings that clippy /// issued @@ -496,96 +215,6 @@ fn build_clippy() -> String { String::from_utf8_lossy(&output.stdout).into_owned() } -/// Read a `lintcheck_crates.toml` file -fn read_crates(toml_path: &Path) -> (Vec, RecursiveOptions) { - let toml_content: String = - fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display())); - let crate_list: SourceList = - toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{e}", toml_path.display())); - // parse the hashmap of the toml file into a list of crates - let tomlcrates: Vec = crate_list.crates.into_values().collect(); - - // flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate => - // multiple Cratesources) - let mut crate_sources = Vec::new(); - for tk in tomlcrates { - if let Some(ref path) = tk.path { - crate_sources.push(CrateSource::Path { - name: tk.name.clone(), - path: PathBuf::from(path), - options: tk.options.clone(), - }); - } else if let Some(ref versions) = tk.versions { - // if we have multiple versions, save each one - for ver in versions { - crate_sources.push(CrateSource::CratesIo { - name: tk.name.clone(), - version: ver.to_string(), - options: tk.options.clone(), - }); - } - } else if tk.git_url.is_some() && tk.git_hash.is_some() { - // otherwise, we should have a git source - crate_sources.push(CrateSource::Git { - name: tk.name.clone(), - url: tk.git_url.clone().unwrap(), - commit: tk.git_hash.clone().unwrap(), - options: tk.options.clone(), - }); - } else { - panic!("Invalid crate source: {tk:?}"); - } - - // if we have a version as well as a git data OR only one git data, something is funky - if tk.versions.is_some() && (tk.git_url.is_some() || tk.git_hash.is_some()) - || tk.git_hash.is_some() != tk.git_url.is_some() - { - eprintln!("tomlkrate: {tk:?}"); - assert_eq!( - tk.git_hash.is_some(), - tk.git_url.is_some(), - "Error: Encountered TomlCrate with only one of git_hash and git_url!" - ); - assert!( - tk.path.is_none() || (tk.git_hash.is_none() && tk.versions.is_none()), - "Error: TomlCrate can only have one of 'git_.*', 'version' or 'path' fields" - ); - unreachable!("Failed to translate TomlCrate into CrateSource!"); - } - } - // sort the crates - crate_sources.sort(); - - (crate_sources, crate_list.recursive) -} - -/// Generate a short list of occurring lints-types and their count -fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { - // count lint type occurrences - let mut counter: HashMap<&String, usize> = HashMap::new(); - warnings - .iter() - .for_each(|wrn| *counter.entry(&wrn.lint).or_insert(0) += 1); - - // collect into a tupled list for sorting - let mut stats: Vec<(&&String, &usize)> = counter.iter().collect(); - // sort by "000{count} {clippy::lintname}" - // to not have a lint with 200 and 2 warnings take the same spot - stats.sort_by_key(|(lint, count)| format!("{count:0>4}, {lint}")); - - let mut header = String::from("| lint | count |\n"); - header.push_str("| -------------------------------------------------- | ----- |\n"); - let stats_string = stats - .iter() - .map(|(lint, count)| format!("| {lint:<50} | {count:>4} |\n")) - .fold(header, |mut table, line| { - table.push_str(&line); - table - }); - - (stats_string, counter) -} - fn main() { // We're being executed as a `RUSTC_WRAPPER` as part of `--recursive` if let Ok(addr) = env::var("LINTCHECK_SERVER") { @@ -738,7 +367,9 @@ fn lintcheck(config: LintcheckConfig) { } let text = match config.format { - OutputFormat::Text | OutputFormat::Markdown => output(&warnings, &raw_ices, clippy_ver, &config), + OutputFormat::Text | OutputFormat::Markdown => { + output::summarize_and_print_changes(&warnings, &raw_ices, clippy_ver, &config) + }, OutputFormat::Json => { if !raw_ices.is_empty() { for ice in raw_ices { @@ -756,135 +387,6 @@ fn lintcheck(config: LintcheckConfig) { fs::write(&config.lintcheck_results_path, text).unwrap(); } -/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`] -fn output(warnings: &[ClippyWarning], ices: &[RustcIce], clippy_ver: String, config: &LintcheckConfig) -> String { - // generate some stats - let (stats_formatted, new_stats) = gather_stats(warnings); - let old_stats = read_stats_from_file(&config.lintcheck_results_path); - - let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.format)).collect(); - all_msgs.sort(); - all_msgs.push("\n\n### Stats:\n\n".into()); - all_msgs.push(stats_formatted); - - let mut text = clippy_ver; // clippy version number on top - text.push_str("\n### Reports\n\n"); - if config.format == OutputFormat::Markdown { - text.push_str("| file | lint | message |\n"); - text.push_str("| --- | --- | --- |\n"); - } - write!(text, "{}", all_msgs.join("")).unwrap(); - text.push_str("\n\n### ICEs:\n"); - for ice in ices { - writeln!(text, "{ice}").unwrap(); - } - - print_stats(old_stats, new_stats, &config.lint_filter); - - text -} - -/// read the previous stats from the lintcheck-log file -fn read_stats_from_file(file_path: &Path) -> HashMap { - let file_content: String = match fs::read_to_string(file_path).ok() { - Some(content) => content, - None => { - return HashMap::new(); - }, - }; - - let lines: Vec = file_content.lines().map(ToString::to_string).collect(); - - lines - .iter() - .skip_while(|line| line.as_str() != "### Stats:") - // Skipping the table header and the `Stats:` label - .skip(4) - .take_while(|line| line.starts_with("| ")) - .filter_map(|line| { - let mut spl = line.split('|'); - // Skip the first `|` symbol - spl.next(); - if let (Some(lint), Some(count)) = (spl.next(), spl.next()) { - Some((lint.trim().to_string(), count.trim().parse::().unwrap())) - } else { - None - } - }) - .collect::>() -} - -/// print how lint counts changed between runs -fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, usize>, lint_filter: &[String]) { - let same_in_both_hashmaps = old_stats - .iter() - .filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val)) - .map(|(k, v)| (k.to_string(), *v)) - .collect::>(); - - let mut old_stats_deduped = old_stats; - let mut new_stats_deduped = new_stats; - - // remove duplicates from both hashmaps - for (k, v) in &same_in_both_hashmaps { - assert!(old_stats_deduped.remove(k) == Some(*v)); - assert!(new_stats_deduped.remove(k) == Some(*v)); - } - - println!("\nStats:"); - - // list all new counts (key is in new stats but not in old stats) - new_stats_deduped - .iter() - .filter(|(new_key, _)| !old_stats_deduped.contains_key::(new_key)) - .for_each(|(new_key, new_value)| { - println!("{new_key} 0 => {new_value}"); - }); - - // list all changed counts (key is in both maps but value differs) - new_stats_deduped - .iter() - .filter(|(new_key, _new_val)| old_stats_deduped.contains_key::(new_key)) - .for_each(|(new_key, new_val)| { - let old_val = old_stats_deduped.get::(new_key).unwrap(); - println!("{new_key} {old_val} => {new_val}"); - }); - - // list all gone counts (key is in old status but not in new stats) - old_stats_deduped - .iter() - .filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key)) - .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key)) - .for_each(|(old_key, old_value)| { - println!("{old_key} {old_value} => 0"); - }); -} - -/// Create necessary directories to run the lintcheck tool. -/// -/// # Panics -/// -/// This function panics if creating one of the dirs fails. -fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) { - fs::create_dir("target/lintcheck/").unwrap_or_else(|err| { - assert_eq!( - err.kind(), - ErrorKind::AlreadyExists, - "cannot create lintcheck target dir" - ); - }); - fs::create_dir(krate_download_dir).unwrap_or_else(|err| { - assert_eq!(err.kind(), ErrorKind::AlreadyExists, "cannot create crate download dir"); - }); - fs::create_dir(extract_dir).unwrap_or_else(|err| { - assert_eq!( - err.kind(), - ErrorKind::AlreadyExists, - "cannot create crate extraction dir" - ); - }); -} - /// Returns the path to the Clippy project directory #[must_use] fn clippy_project_root() -> &'static Path { diff --git a/lintcheck/src/output.rs b/lintcheck/src/output.rs new file mode 100644 index 0000000000000..4bfc554ef9e61 --- /dev/null +++ b/lintcheck/src/output.rs @@ -0,0 +1,235 @@ +use cargo_metadata::diagnostic::{Diagnostic, DiagnosticSpan}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt::{self, Write as _}; +use std::fs; +use std::path::Path; +use std::process::ExitStatus; + +use crate::config::{LintcheckConfig, OutputFormat}; + +/// A single emitted output from clippy being executed on a crate. It may either be a +/// `ClippyWarning`, or a `RustcIce` caused by a panic within clippy. A crate may have many +/// `ClippyWarning`s but a maximum of one `RustcIce` (at which point clippy halts execution). +#[derive(Debug)] +pub enum ClippyCheckOutput { + ClippyWarning(ClippyWarning), + RustcIce(RustcIce), +} + +#[derive(Debug)] +pub struct RustcIce { + pub crate_name: String, + pub ice_content: String, +} + +impl fmt::Display for RustcIce { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:\n{}\n========================================\n", + self.crate_name, self.ice_content + ) + } +} + +impl RustcIce { + pub fn from_stderr_and_status(crate_name: &str, status: ExitStatus, stderr: &str) -> Option { + if status.code().unwrap_or(0) == 101 + /* ice exit status */ + { + Some(Self { + crate_name: crate_name.to_owned(), + ice_content: stderr.to_owned(), + }) + } else { + None + } + } +} + +/// A single warning that clippy issued while checking a `Crate` +#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct ClippyWarning { + pub lint: String, + pub diag: Diagnostic, +} + +#[allow(unused)] +impl ClippyWarning { + pub fn new(mut diag: Diagnostic) -> Option { + let lint = diag.code.clone()?.code; + if !(lint.contains("clippy") || diag.message.contains("clippy")) + || diag.message.contains("could not read cargo metadata") + { + return None; + } + + // --recursive bypasses cargo so we have to strip the rendered output ourselves + let rendered = diag.rendered.as_mut().unwrap(); + *rendered = strip_ansi_escapes::strip_str(&rendered); + + Some(Self { lint, diag }) + } + + pub fn span(&self) -> &DiagnosticSpan { + self.diag.spans.iter().find(|span| span.is_primary).unwrap() + } + + pub fn to_output(&self, format: OutputFormat) -> String { + let span = self.span(); + let mut file = span.file_name.clone(); + let file_with_pos = format!("{file}:{}:{}", span.line_start, span.line_end); + match format { + OutputFormat::Text => format!("{file_with_pos} {} \"{}\"\n", self.lint, self.diag.message), + OutputFormat::Markdown => { + if file.starts_with("target") { + file.insert_str(0, "../"); + } + + let mut output = String::from("| "); + write!(output, "[`{file_with_pos}`]({file}#L{})", span.line_start).unwrap(); + write!(output, r#" | `{:<50}` | "{}" |"#, self.lint, self.diag.message).unwrap(); + output.push('\n'); + output + }, + OutputFormat::Json => unreachable!("JSON output is handled via serde"), + } + } +} + +/// Creates the log file output for [`OutputFormat::Text`] and [`OutputFormat::Markdown`] +pub fn summarize_and_print_changes( + warnings: &[ClippyWarning], + ices: &[RustcIce], + clippy_ver: String, + config: &LintcheckConfig, +) -> String { + // generate some stats + let (stats_formatted, new_stats) = gather_stats(warnings); + let old_stats = read_stats_from_file(&config.lintcheck_results_path); + + let mut all_msgs: Vec = warnings.iter().map(|warn| warn.to_output(config.format)).collect(); + all_msgs.sort(); + all_msgs.push("\n\n### Stats:\n\n".into()); + all_msgs.push(stats_formatted); + + let mut text = clippy_ver; // clippy version number on top + text.push_str("\n### Reports\n\n"); + if config.format == OutputFormat::Markdown { + text.push_str("| file | lint | message |\n"); + text.push_str("| --- | --- | --- |\n"); + } + write!(text, "{}", all_msgs.join("")).unwrap(); + text.push_str("\n\n### ICEs:\n"); + for ice in ices { + writeln!(text, "{ice}").unwrap(); + } + + print_stats(old_stats, new_stats, &config.lint_filter); + + text +} + +/// Generate a short list of occurring lints-types and their count +fn gather_stats(warnings: &[ClippyWarning]) -> (String, HashMap<&String, usize>) { + // count lint type occurrences + let mut counter: HashMap<&String, usize> = HashMap::new(); + warnings + .iter() + .for_each(|wrn| *counter.entry(&wrn.lint).or_insert(0) += 1); + + // collect into a tupled list for sorting + let mut stats: Vec<(&&String, &usize)> = counter.iter().collect(); + // sort by "000{count} {clippy::lintname}" + // to not have a lint with 200 and 2 warnings take the same spot + stats.sort_by_key(|(lint, count)| format!("{count:0>4}, {lint}")); + + let mut header = String::from("| lint | count |\n"); + header.push_str("| -------------------------------------------------- | ----- |\n"); + let stats_string = stats + .iter() + .map(|(lint, count)| format!("| {lint:<50} | {count:>4} |\n")) + .fold(header, |mut table, line| { + table.push_str(&line); + table + }); + + (stats_string, counter) +} + +/// read the previous stats from the lintcheck-log file +fn read_stats_from_file(file_path: &Path) -> HashMap { + let file_content: String = match fs::read_to_string(file_path).ok() { + Some(content) => content, + None => { + return HashMap::new(); + }, + }; + + let lines: Vec = file_content.lines().map(ToString::to_string).collect(); + + lines + .iter() + .skip_while(|line| line.as_str() != "### Stats:") + // Skipping the table header and the `Stats:` label + .skip(4) + .take_while(|line| line.starts_with("| ")) + .filter_map(|line| { + let mut spl = line.split('|'); + // Skip the first `|` symbol + spl.next(); + if let (Some(lint), Some(count)) = (spl.next(), spl.next()) { + Some((lint.trim().to_string(), count.trim().parse::().unwrap())) + } else { + None + } + }) + .collect::>() +} + +/// print how lint counts changed between runs +fn print_stats(old_stats: HashMap, new_stats: HashMap<&String, usize>, lint_filter: &[String]) { + let same_in_both_hashmaps = old_stats + .iter() + .filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val)) + .map(|(k, v)| (k.to_string(), *v)) + .collect::>(); + + let mut old_stats_deduped = old_stats; + let mut new_stats_deduped = new_stats; + + // remove duplicates from both hashmaps + for (k, v) in &same_in_both_hashmaps { + assert!(old_stats_deduped.remove(k) == Some(*v)); + assert!(new_stats_deduped.remove(k) == Some(*v)); + } + + println!("\nStats:"); + + // list all new counts (key is in new stats but not in old stats) + new_stats_deduped + .iter() + .filter(|(new_key, _)| !old_stats_deduped.contains_key::(new_key)) + .for_each(|(new_key, new_value)| { + println!("{new_key} 0 => {new_value}"); + }); + + // list all changed counts (key is in both maps but value differs) + new_stats_deduped + .iter() + .filter(|(new_key, _new_val)| old_stats_deduped.contains_key::(new_key)) + .for_each(|(new_key, new_val)| { + let old_val = old_stats_deduped.get::(new_key).unwrap(); + println!("{new_key} {old_val} => {new_val}"); + }); + + // list all gone counts (key is in old status but not in new stats) + old_stats_deduped + .iter() + .filter(|(old_key, _)| !new_stats_deduped.contains_key::<&String>(old_key)) + .filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key)) + .for_each(|(old_key, old_value)| { + println!("{old_key} {old_value} => 0"); + }); +} diff --git a/lintcheck/src/popular_crates.rs b/lintcheck/src/popular_crates.rs index 880a8bd81f083..ad8fc440c4240 100644 --- a/lintcheck/src/popular_crates.rs +++ b/lintcheck/src/popular_crates.rs @@ -44,7 +44,7 @@ pub(crate) fn fetch(output: PathBuf, number: usize) -> Result<(), Box let mut out = "[crates]\n".to_string(); for Crate { name, max_version } in crates { - writeln!(out, "{name} = {{ name = '{name}', versions = ['{max_version}'] }}").unwrap(); + writeln!(out, "{name} = {{ name = '{name}', version = '{max_version}' }}").unwrap(); } fs::write(output, out)?; diff --git a/lintcheck/src/recursive.rs b/lintcheck/src/recursive.rs index 24dddfe65636a..373ca6f991841 100644 --- a/lintcheck/src/recursive.rs +++ b/lintcheck/src/recursive.rs @@ -3,7 +3,8 @@ //! [`LintcheckServer`] to ask if it should be skipped, and if not sends the stderr of running //! clippy on the crate to the server -use crate::{ClippyWarning, RecursiveOptions}; +use crate::input::RecursiveOptions; +use crate::ClippyWarning; use std::collections::HashSet; use std::io::{BufRead, BufReader, Read, Write}; From e42a380afcd228fad0696d4cb919173b51083212 Mon Sep 17 00:00:00 2001 From: Sander Saares Date: Wed, 10 Jul 2024 08:10:35 +0300 Subject: [PATCH 105/107] Revert accidental "Why restrict this?" change --- clippy_lints/src/operators/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index bf7a581c84863..59834781a58a5 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -636,7 +636,7 @@ declare_clippy_lint! { /// values (apart from zero), except in functions called `*eq*` (which probably /// implement equality for a type involving floats). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Floating point calculations are usually imprecise, so asking if two values are *exactly* /// equal is asking for trouble because arriving at the same logical result via different /// routes (e.g. calculation versus constant) may yield different values. From a987c308cdcfc000d6980487f05222c0799c09e9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 10 Jul 2024 17:18:05 +0200 Subject: [PATCH 106/107] Fix `iter_next_loop.rs` ui test --- tests/ui/iter_next_loop.rs | 2 +- tests/ui/iter_next_loop.stderr | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/ui/iter_next_loop.rs b/tests/ui/iter_next_loop.rs index 548b799de44e8..d425f4da0e81d 100644 --- a/tests/ui/iter_next_loop.rs +++ b/tests/ui/iter_next_loop.rs @@ -3,7 +3,7 @@ fn main() { let x = [1, 2, 3, 4]; - for _ in vec.iter().next() {} + for _ in x.iter().next() {} struct Unrelated(&'static [u8]); impl Unrelated { diff --git a/tests/ui/iter_next_loop.stderr b/tests/ui/iter_next_loop.stderr index 85c23f4e70944..acc55031c3b29 100644 --- a/tests/ui/iter_next_loop.stderr +++ b/tests/ui/iter_next_loop.stderr @@ -1,9 +1,11 @@ -error[E0423]: expected value, found macro `vec` +error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want --> tests/ui/iter_next_loop.rs:6:14 | -LL | for _ in vec.iter().next() {} - | ^^^ not a value +LL | for _ in x.iter().next() {} + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::iter-next-loop` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::iter_next_loop)]` error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0423`. From 3b1266cd312bb58dc2e09edbcabd27b5b7fe7d69 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 11 Jul 2024 15:29:22 +0200 Subject: [PATCH 107/107] Bump nightly version -> 2024-07-11 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 52e8831eeb3a1..a61c22c59f980 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2024-06-27" +channel = "nightly-2024-07-11" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal"