From dfd76c1cbb9bf053b5362f283277d8064d2604a7 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 5 Dec 2024 15:32:12 +0100 Subject: [PATCH 01/57] disallow `repr()` on invalid items Also this generates an error when `repr` is used on a trait method and the `fn_align` feature is not enabled. Looks like that was missed here: https://github.com/rust-lang/rust/pull/110313/files Which first enables the align attribute on trait methods. --- compiler/rustc_passes/src/check_attr.rs | 57 +++++++++++++++++-------- tests/ui/repr/attr-usage-repr.rs | 31 +++++++++++--- tests/ui/repr/attr-usage-repr.stderr | 36 +++++++++++----- 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 074fe77324fa..06d6924b99ff 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -1791,6 +1791,34 @@ impl<'tcx> CheckAttrVisitor<'tcx> { let mut is_simd = false; let mut is_transparent = false; + // catch `repr()` with no arguments, applied to an item (i.e. not `#![repr()]`) + if hints.is_empty() && item.is_some() { + for attr in attrs.iter().filter(|attr| attr.has_name(sym::repr)) { + match target { + Target::Struct | Target::Union | Target::Enum => {} + Target::Fn | Target::Method(_) => { + feature_err( + &self.tcx.sess, + sym::fn_align, + attr.span, + fluent::passes_repr_align_function, + ) + .emit(); + } + _ => { + self.dcx().emit_err( + errors::AttrApplication::StructEnumFunctionMethodUnion { + hint_span: attr.span, + span, + }, + ); + } + } + } + + return; + } + for hint in &hints { if !hint.is_meta_item() { self.dcx().emit_err(errors::ReprIdent { span: hint.span() }); @@ -1823,24 +1851,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } sym::align => { - if let (Target::Fn | Target::Method(MethodKind::Inherent), false) = - (target, self.tcx.features().fn_align()) - { - feature_err( - &self.tcx.sess, - sym::fn_align, - hint.span(), - fluent::passes_repr_align_function, - ) - .emit(); - } - match target { - Target::Struct - | Target::Union - | Target::Enum - | Target::Fn - | Target::Method(_) => {} + Target::Struct | Target::Union | Target::Enum => {} + Target::Fn | Target::Method(_) => { + if !self.tcx.features().fn_align() { + feature_err( + &self.tcx.sess, + sym::fn_align, + hint.span(), + fluent::passes_repr_align_function, + ) + .emit(); + } + } _ => { self.dcx().emit_err( errors::AttrApplication::StructEnumFunctionMethodUnion { diff --git a/tests/ui/repr/attr-usage-repr.rs b/tests/ui/repr/attr-usage-repr.rs index dc0d4f5be2e3..d8515a12e055 100644 --- a/tests/ui/repr/attr-usage-repr.rs +++ b/tests/ui/repr/attr-usage-repr.rs @@ -16,18 +16,39 @@ struct SSimd([f64; 2]); struct SInt(f64, f64); #[repr(C)] -enum EExtern { A, B } +enum EExtern { + A, + B, +} #[repr(align(8))] -enum EAlign { A, B } +enum EAlign { + A, + B, +} #[repr(packed)] //~ ERROR: attribute should be applied to a struct -enum EPacked { A, B } +enum EPacked { + A, + B, +} #[repr(simd)] //~ ERROR: attribute should be applied to a struct -enum ESimd { A, B } +enum ESimd { + A, + B, +} #[repr(i8)] -enum EInt { A, B } +enum EInt { + A, + B, +} + +#[repr()] //~ attribute should be applied to a struct, enum, function, associated function, or union [E0517] +type SirThisIsAType = i32; + +#[repr()] +struct EmptyReprArgumentList(i32); fn main() {} diff --git a/tests/ui/repr/attr-usage-repr.stderr b/tests/ui/repr/attr-usage-repr.stderr index 42f65625a466..a25e68c483f6 100644 --- a/tests/ui/repr/attr-usage-repr.stderr +++ b/tests/ui/repr/attr-usage-repr.stderr @@ -15,21 +15,35 @@ LL | struct SInt(f64, f64); | ---------------------- not an enum error[E0517]: attribute should be applied to a struct or union - --> $DIR/attr-usage-repr.rs:24:8 + --> $DIR/attr-usage-repr.rs:30:8 | -LL | #[repr(packed)] - | ^^^^^^ -LL | enum EPacked { A, B } - | --------------------- not a struct or union +LL | #[repr(packed)] + | ^^^^^^ +LL | / enum EPacked { +LL | | A, +LL | | B, +LL | | } + | |_- not a struct or union error[E0517]: attribute should be applied to a struct - --> $DIR/attr-usage-repr.rs:27:8 + --> $DIR/attr-usage-repr.rs:36:8 | -LL | #[repr(simd)] - | ^^^^ -LL | enum ESimd { A, B } - | ------------------- not a struct +LL | #[repr(simd)] + | ^^^^ +LL | / enum ESimd { +LL | | A, +LL | | B, +LL | | } + | |_- not a struct -error: aborting due to 4 previous errors +error[E0517]: attribute should be applied to a struct, enum, function, associated function, or union + --> $DIR/attr-usage-repr.rs:48:1 + | +LL | #[repr()] + | ^^^^^^^^^ +LL | type SirThisIsAType = i32; + | -------------------------- not a struct, enum, function, associated function, or union + +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0517`. From a4bb0d41bffcbea98d62fb94404704d6d13ea55a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 5 Dec 2024 17:59:05 +0100 Subject: [PATCH 02/57] fix issue 132391 --- tests/crashes/132391.rs | 8 -------- tests/ui/feature-gates/feature-gate-fn_align.rs | 5 +++++ tests/ui/feature-gates/feature-gate-fn_align.stderr | 12 +++++++++++- 3 files changed, 16 insertions(+), 9 deletions(-) delete mode 100644 tests/crashes/132391.rs diff --git a/tests/crashes/132391.rs b/tests/crashes/132391.rs deleted file mode 100644 index 6c8c2c3a8786..000000000000 --- a/tests/crashes/132391.rs +++ /dev/null @@ -1,8 +0,0 @@ -//@ known-bug: #123291 - -trait MyTrait { - #[repr(align)] - fn myfun(); -} - -pub fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-fn_align.rs b/tests/ui/feature-gates/feature-gate-fn_align.rs index ea873dba269c..06784a45d76c 100644 --- a/tests/ui/feature-gates/feature-gate-fn_align.rs +++ b/tests/ui/feature-gates/feature-gate-fn_align.rs @@ -2,3 +2,8 @@ #[repr(align(16))] //~ ERROR `repr(align)` attributes on functions are unstable fn requires_alignment() {} + +trait MyTrait { + #[repr(align)] //~ ERROR `repr(align)` attributes on functions are unstable + fn myfun(); +} diff --git a/tests/ui/feature-gates/feature-gate-fn_align.stderr b/tests/ui/feature-gates/feature-gate-fn_align.stderr index eec332792b76..cd9900c60511 100644 --- a/tests/ui/feature-gates/feature-gate-fn_align.stderr +++ b/tests/ui/feature-gates/feature-gate-fn_align.stderr @@ -8,6 +8,16 @@ LL | #[repr(align(16))] = help: add `#![feature(fn_align)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error: aborting due to 1 previous error +error[E0658]: `repr(align)` attributes on functions are unstable + --> $DIR/feature-gate-fn_align.rs:7:12 + | +LL | #[repr(align)] + | ^^^^^ + | + = note: see issue #82232 for more information + = help: add `#![feature(fn_align)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. From a7adb8217ebe1d92e9406f846834f9c57246b449 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Sun, 29 Dec 2024 07:44:06 -0500 Subject: [PATCH 03/57] Move `format_push_string` and `format_collect` to pedantic --- clippy_dev/src/new_lint.rs | 2 ++ clippy_lints/src/endian_bytes.rs | 3 ++- clippy_lints/src/format_push_string.rs | 2 +- clippy_lints/src/methods/mod.rs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 35dd986ff614..7328ee1ac4f0 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -255,6 +255,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let name_camel = to_camel_case(lint.name); let name_upper = lint_name.to_uppercase(); + #[expect(clippy::format_push_string)] result.push_str(&if enable_msrv { formatdoc!( r" @@ -279,6 +280,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let _: fmt::Result = writeln!(result, "{}", get_lint_declaration(&name_upper, category)); + #[expect(clippy::format_push_string)] result.push_str(&if enable_msrv { formatdoc!( r" diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index 209104c5385c..f3208497b3cd 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -7,6 +7,7 @@ use rustc_middle::lint::in_external_macro; use rustc_middle::ty::Ty; use rustc_session::declare_lint_pass; use rustc_span::Symbol; +use std::fmt::Write; declare_clippy_lint! { /// ### What it does @@ -184,7 +185,7 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix help_str.push_str("either of "); } - help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix))); + write!(help_str, "`{ty}::{}` ", lint.as_name(prefix)).unwrap(); if i != len && !only_one { help_str.push_str("or "); diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 8b1f86cbb913..2b3923ab4686 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -35,7 +35,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.62.0"] pub FORMAT_PUSH_STRING, - restriction, + pedantic, "`format!(..)` appended to existing `String`" } declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 810287fa5416..baae3048d44e 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -3514,7 +3514,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.73.0"] pub FORMAT_COLLECT, - perf, + pedantic, "`format!`ing every element in a collection, then collecting the strings into a new `String`" } From 65b95a2cfb21daccddba238980b545f82ed8fd35 Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Mon, 13 Jan 2025 05:31:12 +0900 Subject: [PATCH 04/57] fix escaping problem in `write_literal` and `print_literal` lint --- clippy_lints/src/write.rs | 2 +- tests/ui/print_literal.fixed | 14 +++++++++++++ tests/ui/print_literal.rs | 15 ++++++++++++++ tests/ui/print_literal.stderr | 38 ++++++++++++++++++++++++++++++++++- tests/ui/write_literal.fixed | 16 +++++++++++++++ tests/ui/write_literal.rs | 17 ++++++++++++++++ tests/ui/write_literal.stderr | 38 ++++++++++++++++++++++++++++++++++- 7 files changed, 137 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 31ae002e47d9..11c14c147776 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -522,7 +522,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { let replacement = match (format_string_is_raw, replace_raw) { (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('"', "\\\"").replace('\\', "\\\\")), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), (true, false) => match conservative_unescape(&replacement) { Ok(unescaped) => Some(unescaped), Err(UnescapeErr::Lint) => None, diff --git a/tests/ui/print_literal.fixed b/tests/ui/print_literal.fixed index 1705a7ff01bb..328e9a9b999f 100644 --- a/tests/ui/print_literal.fixed +++ b/tests/ui/print_literal.fixed @@ -66,3 +66,17 @@ fn main() { println!("mixed: {{hello}} {world}"); } + +fn issue_13959() { + println!("\""); + println!( + " + foo + \\ + \\\\ + \" + \\\" + bar +" + ); +} diff --git a/tests/ui/print_literal.rs b/tests/ui/print_literal.rs index d10b26b5887c..3130d0b6998e 100644 --- a/tests/ui/print_literal.rs +++ b/tests/ui/print_literal.rs @@ -66,3 +66,18 @@ fn main() { println!("mixed: {} {world}", "{hello}"); } + +fn issue_13959() { + println!("{}", r#"""#); + println!( + "{}", + r#" + foo + \ + \\ + " + \" + bar +"# + ); +} diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr index c4cbb8bed707..d967b7c24070 100644 --- a/tests/ui/print_literal.stderr +++ b/tests/ui/print_literal.stderr @@ -192,5 +192,41 @@ LL - println!("mixed: {} {world}", "{hello}"); LL + println!("mixed: {{hello}} {world}"); | -error: aborting due to 16 previous errors +error: literal with an empty format string + --> tests/ui/print_literal.rs:71:20 + | +LL | println!("{}", r#"""#); + | ^^^^^^ + | +help: try + | +LL - println!("{}", r#"""#); +LL + println!("\""); + | + +error: literal with an empty format string + --> tests/ui/print_literal.rs:74:9 + | +LL | / r#" +LL | | foo +LL | | \ +LL | | \\ +... | +LL | | bar +LL | | "# + | |__^ + | +help: try + | +LL ~ " +LL + foo +LL + \\ +LL + \\\\ +LL + \" +LL + \\\" +LL + bar +LL ~ " + | + +error: aborting due to 18 previous errors diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed index 3d216b76cbf3..f1def776e1bc 100644 --- a/tests/ui/write_literal.fixed +++ b/tests/ui/write_literal.fixed @@ -62,3 +62,19 @@ fn main() { writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); //~^ ERROR: literal with an empty format string } + +fn issue_13959() { + let mut v = Vec::new(); + writeln!(v, "\""); + writeln!( + v, + " + foo + \\ + \\\\ + \" + \\\" + bar +" + ); +} diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index 79d6daa2e3b5..1b7df91b47e1 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -62,3 +62,20 @@ fn main() { writeln!(v, "{0} {1} {2}, {3} {4}", "hello", 2, 3, "world", 4); //~^ ERROR: literal with an empty format string } + +fn issue_13959() { + let mut v = Vec::new(); + writeln!(v, "{}", r#"""#); + writeln!( + v, + "{}", + r#" + foo + \ + \\ + " + \" + bar +"# + ); +} diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index 9f4cdfd91e8a..35c93d567cd3 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -144,5 +144,41 @@ LL - writeln!(v, "{0} {1} {2}, {3} {4}", "hello", 2, 3, "world", 4); LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | -error: aborting due to 12 previous errors +error: literal with an empty format string + --> tests/ui/write_literal.rs:68:23 + | +LL | writeln!(v, "{}", r#"""#); + | ^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", r#"""#); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:72:9 + | +LL | / r#" +LL | | foo +LL | | \ +LL | | \\ +... | +LL | | bar +LL | | "# + | |__^ + | +help: try + | +LL ~ " +LL + foo +LL + \\ +LL + \\\\ +LL + \" +LL + \\\" +LL + bar +LL ~ " + | + +error: aborting due to 14 previous errors From 87f7e21009b18a49f306ceb30aca2ef94571ff03 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Tue, 14 Jan 2025 06:06:06 -0500 Subject: [PATCH 05/57] Address review comments --- clippy_dev/src/new_lint.rs | 30 ++++++++++++++------------ clippy_lints/src/format_push_string.rs | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 7328ee1ac4f0..cc4b26867a20 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -255,9 +255,9 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { let name_camel = to_camel_case(lint.name); let name_upper = lint_name.to_uppercase(); - #[expect(clippy::format_push_string)] - result.push_str(&if enable_msrv { - formatdoc!( + if enable_msrv { + let _: fmt::Result = writedoc!( + result, r" use clippy_utils::msrvs::{{self, Msrv}}; use clippy_config::Conf; @@ -266,23 +266,24 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { use rustc_session::impl_lint_pass; " - ) + ); } else { - formatdoc!( + let _: fmt::Result = writedoc!( + result, r" {pass_import} use rustc_lint::{{{context_import}, {pass_type}}}; use rustc_session::declare_lint_pass; " - ) - }); + ); + } let _: fmt::Result = writeln!(result, "{}", get_lint_declaration(&name_upper, category)); - #[expect(clippy::format_push_string)] - result.push_str(&if enable_msrv { - formatdoc!( + if enable_msrv { + let _: fmt::Result = writedoc!( + result, r" pub struct {name_camel} {{ msrv: Msrv, @@ -303,16 +304,17 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { // TODO: Add MSRV level to `clippy_config/src/msrvs.rs` if needed. // TODO: Update msrv config comment in `clippy_config/src/conf.rs` " - ) + ); } else { - formatdoc!( + let _: fmt::Result = writedoc!( + result, r" declare_lint_pass!({name_camel} => [{name_upper}]); impl {pass_type}{pass_lifetimes} for {name_camel} {{}} " - ) - }); + ); + } result } diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 2b3923ab4686..68cc50f39391 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Detects cases where the result of a `format!` call is /// appended to an existing `String`. /// - /// ### Why restrict this? + /// ### Why is this bad? /// Introduces an extra, avoidable heap allocation. /// /// ### Known problems From bbd58d19d4289f2f223600d14c2746b2fefb0071 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Fri, 17 Jan 2025 00:17:13 +0100 Subject: [PATCH 06/57] Do not trigger `[size_of_in_element_count]` for `u8` Counting in bytes for a pointer to `u8` is legitimate and must not trigger the lint. Also, this prevents linting the `{std,core}::ptr::write_bytes` as it manipulates bytes. --- clippy_lints/src/size_of_in_element_count.rs | 10 +- .../size_of_in_element_count/expressions.rs | 17 ++- .../expressions.stderr | 12 +- .../ui/size_of_in_element_count/functions.rs | 45 ++++--- .../size_of_in_element_count/functions.stderr | 110 ++++++++---------- 5 files changed, 85 insertions(+), 109 deletions(-) diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index db1c75fc3dea..12d26b624aca 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -63,8 +63,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, ) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> { - const METHODS: [&str; 11] = [ - "write_bytes", + const METHODS: [&str; 10] = [ "copy_to", "copy_from", "copy_to_nonoverlapping", @@ -79,7 +78,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( if let ExprKind::Call(func, [.., count]) = expr.kind // Find calls to ptr::{copy, copy_nonoverlapping} - // and ptr::{swap_nonoverlapping, write_bytes}, + // and ptr::swap_nonoverlapping, && let ExprKind::Path(ref func_qpath) = func.kind && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id() && matches!(cx.tcx.get_diagnostic_name(def_id), Some( @@ -88,7 +87,6 @@ fn get_pointee_ty_and_count_expr<'tcx>( | sym::ptr_slice_from_raw_parts | sym::ptr_slice_from_raw_parts_mut | sym::ptr_swap_nonoverlapping - | sym::ptr_write_bytes | sym::slice_from_raw_parts | sym::slice_from_raw_parts_mut )) @@ -99,7 +97,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( return Some((pointee_ty, count)); }; if let ExprKind::MethodCall(method_path, ptr_self, [.., count], _) = expr.kind - // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods + // Find calls to copy_{from,to}{,_nonoverlapping} && let method_ident = method_path.ident.as_str() && METHODS.iter().any(|m| *m == method_ident) @@ -121,6 +119,8 @@ impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { instead of a count of elements of `T`"; if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr) + // Using a number of bytes for a byte type isn't suspicious + && pointee_ty != cx.tcx.types.u8 // Find calls to functions with an element count parameter and get // the pointee type and count parameter expression diff --git a/tests/ui/size_of_in_element_count/expressions.rs b/tests/ui/size_of_in_element_count/expressions.rs index 91b7ea3922c5..f405ba200acd 100644 --- a/tests/ui/size_of_in_element_count/expressions.rs +++ b/tests/ui/size_of_in_element_count/expressions.rs @@ -8,11 +8,11 @@ fn main() { const SIZE: usize = 128; const HALF_SIZE: usize = SIZE / 2; const DOUBLE_SIZE: usize = SIZE * 2; - let mut x = [2u8; SIZE]; - let mut y = [2u8; SIZE]; + let mut x = [2u16; SIZE]; + let mut y = [2u16; SIZE]; // Count expression involving multiplication of size_of (Should trigger the lint) - unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; + unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving nested multiplications of size_of (Should trigger the lint) @@ -20,22 +20,19 @@ fn main() { //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving divisions of size_of (Should trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // Count expression involving divisions by size_of (Should not trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / size_of::()) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / size_of::()) }; // Count expression involving divisions by multiple size_of (Should not trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 * size_of::())) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 * size_of::())) }; // Count expression involving recursive divisions by size_of (Should trigger the lint) - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` // No size_of calls (Should not trigger the lint) unsafe { copy(x.as_ptr(), y.as_mut_ptr(), SIZE) }; - - // Different types for pointee and size_of (Should not trigger the lint) - unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() / 2 * SIZE) }; } diff --git a/tests/ui/size_of_in_element_count/expressions.stderr b/tests/ui/size_of_in_element_count/expressions.stderr index 6396afd7f395..74be0d7773df 100644 --- a/tests/ui/size_of_in_element_count/expressions.stderr +++ b/tests/ui/size_of_in_element_count/expressions.stderr @@ -1,8 +1,8 @@ error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:15:62 | -LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` @@ -19,16 +19,16 @@ LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), HALF_SIZE * si error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:23:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE * size_of::() / 2) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/expressions.rs:33:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), DOUBLE_SIZE / (2 / size_of::())) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type diff --git a/tests/ui/size_of_in_element_count/functions.rs b/tests/ui/size_of_in_element_count/functions.rs index 3501cbdf81cf..af18136a1dbe 100644 --- a/tests/ui/size_of_in_element_count/functions.rs +++ b/tests/ui/size_of_in_element_count/functions.rs @@ -11,57 +11,52 @@ fn main() { const SIZE: usize = 128; const HALF_SIZE: usize = SIZE / 2; const DOUBLE_SIZE: usize = SIZE * 2; - let mut x = [2u8; SIZE]; - let mut y = [2u8; SIZE]; + let mut x = [2u16; SIZE]; + let mut y = [2u16; SIZE]; // Count is size_of (Should trigger the lint) - unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; + unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; + unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; + unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; + unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() * SIZE) }; - //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::() * SIZE) }; + unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; + slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); + //~^ ERROR: found a count of bytes instead of a count of elements of `T` + slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); + unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); + unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; + unsafe { y.as_mut_ptr().sub(size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; + y.as_ptr().wrapping_sub(size_of::()); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - - unsafe { y.as_mut_ptr().sub(size_of::()) }; + unsafe { y.as_ptr().add(size_of::()) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_ptr().wrapping_sub(size_of::()); + y.as_mut_ptr().wrapping_add(size_of::()); //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_ptr().add(size_of::()) }; + unsafe { y.as_ptr().offset(size_of::() as isize) }; //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_mut_ptr().wrapping_add(size_of::()); - //~^ ERROR: found a count of bytes instead of a count of elements of `T` - unsafe { y.as_ptr().offset(size_of::() as isize) }; - //~^ ERROR: found a count of bytes instead of a count of elements of `T` - y.as_mut_ptr().wrapping_offset(size_of::() as isize); + y.as_mut_ptr().wrapping_offset(size_of::() as isize); //~^ ERROR: found a count of bytes instead of a count of elements of `T` } diff --git a/tests/ui/size_of_in_element_count/functions.stderr b/tests/ui/size_of_in_element_count/functions.stderr index abde7dc7cd2d..de54789b2251 100644 --- a/tests/ui/size_of_in_element_count/functions.stderr +++ b/tests/ui/size_of_in_element_count/functions.stderr @@ -1,8 +1,8 @@ error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:18:68 + --> tests/ui/size_of_in_element_count/functions.rs:18:69 | -LL | unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { copy_nonoverlapping::(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type = note: `-D clippy::size-of-in-element-count` implied by `-D warnings` @@ -19,40 +19,40 @@ LL | unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:23:49 | -LL | unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { x.as_ptr().copy_to(y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:25:64 | -LL | unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { x.as_ptr().copy_to_nonoverlapping(y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:27:51 | -LL | unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().copy_from(x.as_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:29:66 | -LL | unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().copy_from_nonoverlapping(x.as_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` --> tests/ui/size_of_in_element_count/functions.rs:32:47 | -LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type @@ -65,108 +65,92 @@ LL | unsafe { copy(x.as_ptr(), y.as_mut_ptr(), size_of_val(&x[0])) }; = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:37:46 + --> tests/ui/size_of_in_element_count/functions.rs:37:66 | -LL | unsafe { y.as_mut_ptr().write_bytes(0u8, size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:39:47 + --> tests/ui/size_of_in_element_count/functions.rs:40:46 | -LL | unsafe { write_bytes(y.as_mut_ptr(), 0u8, size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:42:66 + --> tests/ui/size_of_in_element_count/functions.rs:42:38 | -LL | unsafe { swap_nonoverlapping(y.as_mut_ptr(), x.as_mut_ptr(), size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:45:46 + --> tests/ui/size_of_in_element_count/functions.rs:45:49 | -LL | slice_from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:47:38 + --> tests/ui/size_of_in_element_count/functions.rs:47:41 | -LL | slice_from_raw_parts(y.as_ptr(), size_of::() * SIZE); - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; + | ^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:50:49 + --> tests/ui/size_of_in_element_count/functions.rs:50:33 | -LL | unsafe { from_raw_parts_mut(y.as_mut_ptr(), size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | unsafe { y.as_mut_ptr().sub(size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:52:41 + --> tests/ui/size_of_in_element_count/functions.rs:52:29 | -LL | unsafe { from_raw_parts(y.as_ptr(), size_of::() * SIZE) }; - | ^^^^^^^^^^^^^^^^^^^^^^ +LL | y.as_ptr().wrapping_sub(size_of::()); + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:55:33 + --> tests/ui/size_of_in_element_count/functions.rs:54:29 | -LL | unsafe { y.as_mut_ptr().sub(size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_ptr().add(size_of::()) }; + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:57:29 + --> tests/ui/size_of_in_element_count/functions.rs:56:33 | -LL | y.as_ptr().wrapping_sub(size_of::()); - | ^^^^^^^^^^^^^^^ +LL | y.as_mut_ptr().wrapping_add(size_of::()); + | ^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:59:29 + --> tests/ui/size_of_in_element_count/functions.rs:58:32 | -LL | unsafe { y.as_ptr().add(size_of::()) }; - | ^^^^^^^^^^^^^^^ +LL | unsafe { y.as_ptr().offset(size_of::() as isize) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:61:33 + --> tests/ui/size_of_in_element_count/functions.rs:60:36 | -LL | y.as_mut_ptr().wrapping_add(size_of::()); - | ^^^^^^^^^^^^^^^ +LL | y.as_mut_ptr().wrapping_offset(size_of::() as isize); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type -error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:63:32 - | -LL | unsafe { y.as_ptr().offset(size_of::() as isize) }; - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type - -error: found a count of bytes instead of a count of elements of `T` - --> tests/ui/size_of_in_element_count/functions.rs:65:36 - | -LL | y.as_mut_ptr().wrapping_offset(size_of::() as isize); - | ^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: use a count of elements instead of a count of bytes, it already gets multiplied by the size of the type - -error: aborting due to 21 previous errors +error: aborting due to 19 previous errors From 5bd989a46f8f2806ad7efd45fabe4f09f5bee025 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 19 Jan 2025 15:26:42 +0500 Subject: [PATCH 07/57] Update version for unneeded_struct_pattern --- clippy_lints/src/unneeded_struct_pattern.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/unneeded_struct_pattern.rs b/clippy_lints/src/unneeded_struct_pattern.rs index 40ba70d451db..a74eab8b6ae5 100644 --- a/clippy_lints/src/unneeded_struct_pattern.rs +++ b/clippy_lints/src/unneeded_struct_pattern.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// None => 0, /// }; /// ``` - #[clippy::version = "1.83.0"] + #[clippy::version = "1.86.0"] pub UNNEEDED_STRUCT_PATTERN, style, "using struct pattern to match against unit variant" From 7aae4f462a45bb64e336879278f4472445e7e7b6 Mon Sep 17 00:00:00 2001 From: lapla-cogito Date: Sat, 4 Jan 2025 00:00:40 +0900 Subject: [PATCH 08/57] auto-fix for redundant_else lint --- clippy_lints/src/redundant_else.rs | 46 ++++++++- tests/ui/redundant_else.fixed | 154 +++++++++++++++++++++++++++++ tests/ui/redundant_else.stderr | 77 +++++++++++---- 3 files changed, 252 insertions(+), 25 deletions(-) create mode 100644 tests/ui/redundant_else.fixed diff --git a/clippy_lints/src/redundant_else.rs b/clippy_lints/src/redundant_else.rs index 6a1d40334e75..5c7a9b4bf4f8 100644 --- a/clippy_lints/src/redundant_else.rs +++ b/clippy_lints/src/redundant_else.rs @@ -1,9 +1,13 @@ -use clippy_utils::diagnostics::span_lint_and_help; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet}; use rustc_ast::ast::{Block, Expr, ExprKind, Stmt, StmtKind}; use rustc_ast::visit::{Visitor, walk_expr}; +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::borrow::Cow; declare_clippy_lint! { /// ### What it does @@ -77,13 +81,27 @@ impl EarlyLintPass for RedundantElse { _ => break, } } - span_lint_and_help( + + let mut app = Applicability::MachineApplicable; + if let ExprKind::Block(block, _) = &els.kind { + for stmt in &block.stmts { + // If the `else` block contains a local binding or a macro invocation, Clippy shouldn't auto-fix it + if matches!(&stmt.kind, StmtKind::Let(_) | StmtKind::MacCall(_)) { + app = Applicability::Unspecified; + break; + } + } + } + + // FIXME: The indentation of the suggestion would be the same as the one of the macro invocation in this implementation, see https://github.com/rust-lang/rust-clippy/pull/13936#issuecomment-2569548202 + span_lint_and_sugg( cx, REDUNDANT_ELSE, - els.span, + els.span.with_lo(then.span.hi()), "redundant else block", - None, "remove the `else` block and move the contents out", + make_sugg(cx, els.span, "..", Some(expr.span)).to_string(), + app, ); } } @@ -138,3 +156,23 @@ impl BreakVisitor { self.check(stmt, Self::visit_stmt) } } + +// Extract the inner contents of an `else` block str +// e.g. `{ foo(); bar(); }` -> `foo(); bar();` +fn extract_else_block(mut block: &str) -> String { + block = block.strip_prefix("{").unwrap_or(block); + block = block.strip_suffix("}").unwrap_or(block); + block.trim_end().to_string() +} + +fn make_sugg<'a>( + cx: &EarlyContext<'_>, + els_span: Span, + default: &'a str, + indent_relative_to: Option, +) -> Cow<'a, str> { + let extracted = extract_else_block(&snippet(cx, els_span, default)); + let indent = indent_relative_to.and_then(|s| indent_of(cx, s)); + + reindent_multiline(extracted.into(), false, indent) +} diff --git a/tests/ui/redundant_else.fixed b/tests/ui/redundant_else.fixed new file mode 100644 index 000000000000..47aa79302d2c --- /dev/null +++ b/tests/ui/redundant_else.fixed @@ -0,0 +1,154 @@ +#![warn(clippy::redundant_else)] +#![allow(clippy::needless_return, clippy::if_same_then_else, clippy::needless_late_init)] + +fn main() { + loop { + // break + if foo() { + println!("Love your neighbor;"); + break; + } + //~^ ERROR: redundant else block + println!("yet don't pull down your hedge."); + // continue + if foo() { + println!("He that lies down with Dogs,"); + continue; + } + //~^ ERROR: redundant else block + println!("shall rise up with fleas."); + // match block + if foo() { + match foo() { + 1 => break, + _ => return, + } + } + //~^ ERROR: redundant else block + println!("You may delay, but time will not."); + } + // else if + if foo() { + return; + } else if foo() { + return; + } + //~^ ERROR: redundant else block + println!("A fat kitchen makes a lean will."); + // let binding outside of block + let _ = { + if foo() { + return; + } + //~^ ERROR: redundant else block + 1 + }; + // else if with let binding outside of block + let _ = { + if foo() { + return; + } else if foo() { + return; + } + //~^ ERROR: redundant else block + 2 + }; + // inside if let + let _ = if let Some(1) = foo() { + let _ = 1; + if foo() { + return; + } + //~^ ERROR: redundant else block + 1 + } else { + 1 + }; + + // + // non-lint cases + // + + // sanity check + if foo() { + let _ = 1; + } else { + println!("Who is wise? He that learns from every one."); + } + // else if without else + if foo() { + return; + } else if foo() { + foo() + }; + // nested if return + if foo() { + if foo() { + return; + } + } else { + foo() + }; + // match with non-breaking branch + if foo() { + match foo() { + 1 => foo(), + _ => return, + } + } else { + println!("Three may keep a secret, if two of them are dead."); + } + // let binding + let _ = if foo() { + return; + } else { + 1 + }; + // assign + let mut a; + a = if foo() { + return; + } else { + 1 + }; + // assign-op + a += if foo() { + return; + } else { + 1 + }; + // if return else if else + if foo() { + return; + } else if foo() { + 1 + } else { + 2 + }; + // if else if return else + if foo() { + 1 + } else if foo() { + return; + } else { + 2 + }; + // else if with let binding + let _ = if foo() { + return; + } else if foo() { + return; + } else { + 2 + }; + // inside function call + Box::new(if foo() { + return; + } else { + 1 + }); +} + +fn foo() -> T { + unimplemented!("I'm not Santa Claus") +} diff --git a/tests/ui/redundant_else.stderr b/tests/ui/redundant_else.stderr index b649a210b5fa..ecc16f7cda5e 100644 --- a/tests/ui/redundant_else.stderr +++ b/tests/ui/redundant_else.stderr @@ -1,88 +1,123 @@ error: redundant else block - --> tests/ui/redundant_else.rs:10:16 + --> tests/ui/redundant_else.rs:10:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("yet don't pull down your hedge."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out = note: `-D clippy::redundant-else` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::redundant_else)]` +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("yet don't pull down your hedge."); + | error: redundant else block - --> tests/ui/redundant_else.rs:18:16 + --> tests/ui/redundant_else.rs:18:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("shall rise up with fleas."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("shall rise up with fleas."); + | error: redundant else block - --> tests/ui/redundant_else.rs:28:16 + --> tests/ui/redundant_else.rs:28:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | println!("You may delay, but time will not."); LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("You may delay, but time will not."); + | error: redundant else block - --> tests/ui/redundant_else.rs:38:12 + --> tests/ui/redundant_else.rs:38:6 | LL | } else { - | ____________^ + | ______^ LL | | LL | | println!("A fat kitchen makes a lean will."); LL | | } | |_____^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + println!("A fat kitchen makes a lean will."); + | error: redundant else block - --> tests/ui/redundant_else.rs:46:16 + --> tests/ui/redundant_else.rs:46:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 1 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 1 + | error: redundant else block - --> tests/ui/redundant_else.rs:57:16 + --> tests/ui/redundant_else.rs:57:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 2 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 2 + | error: redundant else block - --> tests/ui/redundant_else.rs:67:16 + --> tests/ui/redundant_else.rs:67:10 | LL | } else { - | ________________^ + | __________^ LL | | LL | | 1 LL | | } | |_________^ | - = help: remove the `else` block and move the contents out +help: remove the `else` block and move the contents out + | +LL ~ } +LL + +LL + 1 + | error: aborting due to 7 previous errors From f0b99b2b388ebfd3b5b2604737d2b39db64f9081 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 20 Jan 2025 11:47:19 +0100 Subject: [PATCH 09/57] `needless_option_take`: add autofix --- .../src/methods/needless_option_take.rs | 18 ++++-- tests/ui/needless_option_take.fixed | 58 +++++++++++++++++++ tests/ui/needless_option_take.stderr | 36 +++++++++--- 3 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 tests/ui/needless_option_take.fixed diff --git a/clippy_lints/src/methods/needless_option_take.rs b/clippy_lints/src/methods/needless_option_take.rs index c41ce2481d74..88b9c69f6f94 100644 --- a/clippy_lints/src/methods/needless_option_take.rs +++ b/clippy_lints/src/methods/needless_option_take.rs @@ -1,5 +1,6 @@ -use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, QPath}; use rustc_lint::LateContext; use rustc_span::sym; @@ -10,13 +11,22 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, recv: &' // Checks if expression type is equal to sym::Option and if the expr is not a syntactic place if !recv.is_syntactic_place_expr() && is_expr_option(cx, recv) { if let Some(function_name) = source_of_temporary_value(recv) { - span_lint_and_note( + span_lint_and_then( cx, NEEDLESS_OPTION_TAKE, expr.span, "called `Option::take()` on a temporary value", - None, - format!("`{function_name}` creates a temporary value, so calling take() has no effect"), + |diag| { + diag.note(format!( + "`{function_name}` creates a temporary value, so calling take() has no effect" + )); + diag.span_suggestion( + expr.span.with_lo(recv.span.hi()), + "remove", + "", + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/tests/ui/needless_option_take.fixed b/tests/ui/needless_option_take.fixed new file mode 100644 index 000000000000..6514b67ef7a7 --- /dev/null +++ b/tests/ui/needless_option_take.fixed @@ -0,0 +1,58 @@ +struct MyStruct; + +impl MyStruct { + pub fn get_option() -> Option { + todo!() + } +} + +fn return_option() -> Option { + todo!() +} + +fn main() { + println!("Testing non erroneous option_take_on_temporary"); + let mut option = Some(1); + let _ = Box::new(move || option.take().unwrap()); + + println!("Testing non erroneous option_take_on_temporary"); + let x = Some(3); + x.as_ref(); + + let x = Some(3); + x.as_ref(); + //~^ ERROR: called `Option::take()` on a temporary value + + println!("Testing non erroneous option_take_on_temporary"); + let mut x = Some(3); + let y = x.as_mut(); + + let mut x = Some(3); + let y = x.as_mut(); + //~^ ERROR: called `Option::take()` on a temporary value + let y = x.replace(289); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = Some(3).as_mut(); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = Option::as_mut(&mut x); + //~^ ERROR: called `Option::take()` on a temporary value + + let x = return_option(); + let x = return_option(); + //~^ ERROR: called `Option::take()` on a temporary value + + let x = MyStruct::get_option(); + let x = MyStruct::get_option(); + //~^ ERROR: called `Option::take()` on a temporary value + + let mut my_vec = vec![1, 2, 3]; + my_vec.push(4); + let y = my_vec.first(); + let y = my_vec.first(); + //~^ ERROR: called `Option::take()` on a temporary value + + let y = my_vec.first(); + //~^ ERROR: called `Option::take()` on a temporary value +} diff --git a/tests/ui/needless_option_take.stderr b/tests/ui/needless_option_take.stderr index e036bd53170a..3fc339ed79e2 100644 --- a/tests/ui/needless_option_take.stderr +++ b/tests/ui/needless_option_take.stderr @@ -2,7 +2,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:23:5 | LL | x.as_ref().take(); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^------- + | | + | help: remove | = note: `as_ref` creates a temporary value, so calling take() has no effect = note: `-D clippy::needless-option-take` implied by `-D warnings` @@ -12,7 +14,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:31:13 | LL | let y = x.as_mut().take(); - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -20,7 +24,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:33:13 | LL | let y = x.replace(289).take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `replace` creates a temporary value, so calling take() has no effect @@ -28,7 +34,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:36:13 | LL | let y = Some(3).as_mut().take(); - | ^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -36,7 +44,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:39:13 | LL | let y = Option::as_mut(&mut x).take(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `as_mut` creates a temporary value, so calling take() has no effect @@ -44,7 +54,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:43:13 | LL | let x = return_option().take(); - | ^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `return_option` creates a temporary value, so calling take() has no effect @@ -52,7 +64,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:47:13 | LL | let x = MyStruct::get_option().take(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `get_option` creates a temporary value, so calling take() has no effect @@ -60,7 +74,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:53:13 | LL | let y = my_vec.first().take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `first` creates a temporary value, so calling take() has no effect @@ -68,7 +84,9 @@ error: called `Option::take()` on a temporary value --> tests/ui/needless_option_take.rs:56:13 | LL | let y = my_vec.first().take(); - | ^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^------- + | | + | help: remove | = note: `first` creates a temporary value, so calling take() has no effect From e485cc5e0217fbb3f495ee0db56d6dcb5f36de22 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Sat, 25 Jan 2025 21:18:16 +0000 Subject: [PATCH 10/57] Simplify slice indexing in next trait solver --- .../src/solve/eval_ctxt/canonical.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs index e99cd3d27270..a56ffbc85002 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs @@ -442,13 +442,11 @@ where { // In case any fresh inference variables have been created between `state` // and the previous instantiation, extend `orig_values` for it. - assert!(orig_values.len() <= state.value.var_values.len()); - for &arg in &state.value.var_values.var_values.as_slice() - [orig_values.len()..state.value.var_values.len()] - { - let unconstrained = delegate.fresh_var_for_kind_with_span(arg, span); - orig_values.push(unconstrained); - } + orig_values.extend( + state.value.var_values.var_values.as_slice()[orig_values.len()..] + .iter() + .map(|&arg| delegate.fresh_var_for_kind_with_span(arg, span)), + ); let instantiation = EvalCtxt::compute_query_response_instantiation_values(delegate, orig_values, &state); From 13be95ab11d0ffc5bb98a08dc6d3bb18ef6e0a77 Mon Sep 17 00:00:00 2001 From: Andre Bogus Date: Sun, 29 Dec 2024 17:42:33 +0100 Subject: [PATCH 11/57] new `manual_option_as_slice` lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_option_as_slice.rs | 225 +++++++++++++++++++++ clippy_lints/src/matches/match_as_ref.rs | 10 +- clippy_utils/src/lib.rs | 8 + clippy_utils/src/msrvs.rs | 3 + tests/ui/manual_option_as_slice.fixed | 62 ++++++ tests/ui/manual_option_as_slice.rs | 71 +++++++ tests/ui/manual_option_as_slice.stderr | 58 ++++++ 10 files changed, 432 insertions(+), 9 deletions(-) create mode 100644 clippy_lints/src/manual_option_as_slice.rs create mode 100644 tests/ui/manual_option_as_slice.fixed create mode 100644 tests/ui/manual_option_as_slice.rs create mode 100644 tests/ui/manual_option_as_slice.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5b8fb392ff..523c7a970aec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5764,6 +5764,7 @@ Released 2018-09-13 [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive [`manual_ok_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_err [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or +[`manual_option_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_option_as_slice [`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_range_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_patterns diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 86410c16d95f..02f6488aa019 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -320,6 +320,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::manual_let_else::MANUAL_LET_ELSE_INFO, crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO, crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO, + crate::manual_option_as_slice::MANUAL_OPTION_AS_SLICE_INFO, crate::manual_range_patterns::MANUAL_RANGE_PATTERNS_INFO, crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO, crate::manual_retain::MANUAL_RETAIN_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4b700673d0f8..8887ab7ec0d7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -217,6 +217,7 @@ mod manual_is_power_of_two; mod manual_let_else; mod manual_main_separator_str; mod manual_non_exhaustive; +mod manual_option_as_slice; mod manual_range_patterns; mod manual_rem_euclid; mod manual_retain; @@ -976,5 +977,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); store.register_late_pass(|_| Box::::default()); store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); + store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs new file mode 100644 index 000000000000..5c40c945c690 --- /dev/null +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -0,0 +1,225 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::{is_none_arm, msrvs, peel_hir_expr_refs}; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Span, Symbol, sym}; + +declare_clippy_lint! { + /// ### What it does + /// This detects various manual reimplementations of `Option::as_slice`. + /// + /// ### Why is this bad? + /// Those implementations are both more complex than calling `as_slice` + /// and unlike that incur a branch, pessimizing performance and leading + /// to more generated code. + /// + /// ### Example + /// ```no_run + ///# let opt = Some(1); + /// _ = opt.as_ref().map_or(&[][..], std::slice::from_ref); + /// _ = match opt.as_ref() { + /// Some(f) => std::slice::from_ref(f), + /// None => &[], + /// }; + /// ``` + /// Use instead: + /// ```no_run + ///# let opt = Some(1); + /// _ = opt.as_slice(); + /// _ = opt.as_slice(); + /// ``` + #[clippy::version = "1.85.0"] + pub MANUAL_OPTION_AS_SLICE, + complexity, + "manual `Option::as_slice`" +} + +pub struct ManualOptionAsSlice { + msrv: msrvs::Msrv, +} + +impl ManualOptionAsSlice { + pub fn new(conf: &Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + } + } +} + +impl_lint_pass!(ManualOptionAsSlice => [MANUAL_OPTION_AS_SLICE]); + +impl LateLintPass<'_> for ManualOptionAsSlice { + extract_msrv_attr!(LateContext); + + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + let span = expr.span; + if span.from_expansion() + || !self.msrv.meets(if clippy_utils::is_in_const_context(cx) { + msrvs::CONST_OPTION_AS_SLICE + } else { + msrvs::OPTION_AS_SLICE + }) + { + return; + } + match expr.kind { + ExprKind::Match(scrutinee, [arm1, arm2], _) => { + if is_none_arm(cx, arm2) && check_arms(cx, arm2, arm1) + || is_none_arm(cx, arm1) && check_arms(cx, arm1, arm2) + { + check_as_ref(cx, scrutinee, span); + } + }, + ExprKind::If(cond, then, Some(other)) => { + if let ExprKind::Let(let_expr) = cond.kind + && let Some(binding) = extract_ident_from_some_pat(cx, let_expr.pat) + && check_some_body(cx, binding, then) + && is_empty_slice(cx, other.peel_blocks()) + { + check_as_ref(cx, let_expr.init, span); + } + }, + ExprKind::MethodCall(seg, callee, [], _) => { + if seg.ident.name.as_str() == "unwrap_or_default" { + check_map(cx, callee, span); + } + }, + ExprKind::MethodCall(seg, callee, [or], _) => match seg.ident.name.as_str() { + "unwrap_or" => { + if is_empty_slice(cx, or) { + check_map(cx, callee, span); + } + }, + "unwrap_or_else" => { + if returns_empty_slice(cx, or) { + check_map(cx, callee, span); + } + }, + _ => {}, + }, + ExprKind::MethodCall(seg, callee, [or_else, map], _) => match seg.ident.name.as_str() { + "map_or" => { + if is_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) { + check_as_ref(cx, callee, span); + } + }, + "map_or_else" => { + if returns_empty_slice(cx, or_else) && is_slice_from_ref(cx, map) { + check_as_ref(cx, callee, span); + } + }, + _ => {}, + }, + _ => {}, + } + } +} + +fn check_map(cx: &LateContext<'_>, map: &Expr<'_>, span: Span) { + if let ExprKind::MethodCall(seg, callee, [mapping], _) = map.kind + && seg.ident.name == sym::map + && is_slice_from_ref(cx, mapping) + { + check_as_ref(cx, callee, span); + } +} + +fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span) { + if let ExprKind::MethodCall(seg, callee, [], _) = expr.kind + && seg.ident.name == sym::as_ref + && let ty::Adt(adtdef, ..) = cx.typeck_results().expr_ty(callee).kind() + && cx.tcx.is_diagnostic_item(sym::Option, adtdef.did()) + { + if let Some(snippet) = clippy_utils::source::snippet_opt(cx, callee.span) { + span_lint_and_sugg( + cx, + MANUAL_OPTION_AS_SLICE, + span, + "use `Option::as_slice`", + "use", + format!("{snippet}.as_slice()"), + Applicability::MachineApplicable, + ); + } else { + span_lint(cx, MANUAL_OPTION_AS_SLICE, span, "use `Option_as_slice`"); + } + } +} + +fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { + if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind + && let Res::Def(DefKind::Ctor(..), def_id) = path.res + && let PatKind::Binding(_mode, _hir_id, ident, _inner_pat) = binding.kind + && clippy_utils::is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) + { + Some(ident.name) + } else { + None + } +} + +/// Returns true if `expr` is `std::slice::from_ref()`. Used in `if let`s. +fn check_some_body(cx: &LateContext<'_>, name: Symbol, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(slice_from_ref, [arg]) = expr.peel_blocks().kind + && is_slice_from_ref(cx, slice_from_ref) + && let ExprKind::Path(QPath::Resolved(None, path)) = arg.kind + && let [seg] = path.segments + { + seg.ident.name == name + } else { + false + } +} + +fn check_arms(cx: &LateContext<'_>, none_arm: &Arm<'_>, some_arm: &Arm<'_>) -> bool { + if none_arm.guard.is_none() + && some_arm.guard.is_none() + && is_empty_slice(cx, none_arm.body) + && let Some(name) = extract_ident_from_some_pat(cx, some_arm.pat) + { + check_some_body(cx, name, some_arm.body) + } else { + false + } +} + +fn returns_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Path(_) => clippy_utils::is_path_diagnostic_item(cx, expr, sym::default_fn), + ExprKind::Closure(cl) => is_empty_slice(cx, cx.tcx.hir().body(cl.body).value), + _ => false, + } +} + +/// Returns if expr returns an empty slice. If: +/// - An indexing operation to an empty array with a built-in range. `[][..]` +/// - An indexing operation with a zero-ended range. `expr[..0]` +/// - A reference to an empty array. `&[]` +/// - Or a call to `Default::default`. +fn is_empty_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let expr = peel_hir_expr_refs(expr.peel_blocks()).0; + match expr.kind { + ExprKind::Index(arr, range, _) => match arr.kind { + ExprKind::Array([]) => is_range_literal(range), + ExprKind::Array(_) => { + let Some(range) = clippy_utils::higher::Range::hir(range) else { + return false; + }; + range.end.is_some_and(|e| clippy_utils::is_integer_const(cx, e, 0)) + }, + _ => false, + }, + ExprKind::Array([]) => true, + ExprKind::Call(def, []) => clippy_utils::is_path_diagnostic_item(cx, def, sym::default_fn), + _ => false, + } +} + +fn is_slice_from_ref(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + clippy_utils::is_expr_path_def_path(cx, expr, &["core", "slice", "raw", "from_ref"]) +} diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 6c123649afc2..1cb4b512a30e 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks}; +use clippy_utils::{is_none_arm, is_res_lang_ctor, path_res, peel_blocks}; use rustc_errors::Applicability; use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; use rustc_lint::LateContext; @@ -55,14 +55,6 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: } } -// Checks if arm has the form `None => None` -fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { - matches!( - arm.pat.kind, - PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionNone) - ) -} - // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 6869078ba0ad..2c97482aa316 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -342,6 +342,14 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } +// Checks if arm has the form `None => _` +pub fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { + matches!( + arm.pat.kind, + PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id),OptionNone) + ) +} + /// Checks if the given `QPath` belongs to a type alias. pub fn is_ty_alias(qpath: &QPath<'_>) -> bool { match *qpath { diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index d73cb7e35611..32365e95cd20 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -1,4 +1,5 @@ use rustc_ast::attr::AttributeExt; + use rustc_attr_parsing::{RustcVersion, parse_version}; use rustc_session::Session; use rustc_span::{Symbol, sym}; @@ -18,12 +19,14 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,84,0 { CONST_OPTION_AS_SLICE } 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION } 1,80,0 { BOX_INTO_ITER, LAZY_CELL } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } + 1,75,0 { OPTION_AS_SLICE } 1,74,0 { REPR_RUST } 1,73,0 { MANUAL_DIV_CEIL } 1,71,0 { TUPLE_ARRAY_CONVERSIONS, BUILD_HASHER_HASH_ONE } diff --git a/tests/ui/manual_option_as_slice.fixed b/tests/ui/manual_option_as_slice.fixed new file mode 100644 index 000000000000..48337d7654de --- /dev/null +++ b/tests/ui/manual_option_as_slice.fixed @@ -0,0 +1,62 @@ +#![warn(clippy::manual_option_as_slice)] +#![allow(clippy::redundant_closure, clippy::unwrap_or_default)] + +fn check(x: Option) { + _ = x.as_slice(); + + _ = x.as_slice(); + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + _ = x.as_slice(); + //~^ manual_option_as_slice + + { + use std::slice::from_ref; + _ = x.as_slice(); + //~^ manual_option_as_slice + } + + // possible false positives + let y = x.as_ref(); + _ = match y { + // as_ref outside + Some(f) => &[f][..], + None => &[][..], + }; + _ = match x.as_ref() { + Some(f) => std::slice::from_ref(f), + None => &[0], + }; + _ = match x.as_ref() { + Some(42) => &[23], + Some(f) => std::slice::from_ref(f), + None => &[], + }; + let b = &[42]; + _ = if let Some(_f) = x.as_ref() { + std::slice::from_ref(b) + } else { + &[] + }; + _ = x.as_ref().map_or(&[42][..], std::slice::from_ref); + _ = x.as_ref().map_or_else(|| &[42][..1], std::slice::from_ref); + _ = x.as_ref().map(|f| std::slice::from_ref(f)).unwrap_or_default(); +} + +#[clippy::msrv = "1.74"] +fn check_msrv(x: Option) { + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +} + +fn main() { + check(Some(1)); + check_msrv(Some(175)); +} diff --git a/tests/ui/manual_option_as_slice.rs b/tests/ui/manual_option_as_slice.rs new file mode 100644 index 000000000000..561a8b534014 --- /dev/null +++ b/tests/ui/manual_option_as_slice.rs @@ -0,0 +1,71 @@ +#![warn(clippy::manual_option_as_slice)] +#![allow(clippy::redundant_closure, clippy::unwrap_or_default)] + +fn check(x: Option) { + _ = match x.as_ref() { + //~^ manual_option_as_slice + Some(f) => std::slice::from_ref(f), + None => &[], + }; + + _ = if let Some(f) = x.as_ref() { + //~^ manual_option_as_slice + std::slice::from_ref(f) + } else { + &[] + }; + + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); + //~^ manual_option_as_slice + + _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); + //~^ manual_option_as_slice + + _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); + //~^ manual_option_as_slice + + _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); + //~^ manual_option_as_slice + + { + use std::slice::from_ref; + _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); + //~^ manual_option_as_slice + } + + // possible false positives + let y = x.as_ref(); + _ = match y { + // as_ref outside + Some(f) => &[f][..], + None => &[][..], + }; + _ = match x.as_ref() { + Some(f) => std::slice::from_ref(f), + None => &[0], + }; + _ = match x.as_ref() { + Some(42) => &[23], + Some(f) => std::slice::from_ref(f), + None => &[], + }; + let b = &[42]; + _ = if let Some(_f) = x.as_ref() { + std::slice::from_ref(b) + } else { + &[] + }; + _ = x.as_ref().map_or(&[42][..], std::slice::from_ref); + _ = x.as_ref().map_or_else(|| &[42][..1], std::slice::from_ref); + _ = x.as_ref().map(|f| std::slice::from_ref(f)).unwrap_or_default(); +} + +#[clippy::msrv = "1.74"] +fn check_msrv(x: Option) { + _ = x.as_ref().map_or(&[][..], std::slice::from_ref); +} + +fn main() { + check(Some(1)); + check_msrv(Some(175)); +} diff --git a/tests/ui/manual_option_as_slice.stderr b/tests/ui/manual_option_as_slice.stderr new file mode 100644 index 000000000000..569269d3e2b7 --- /dev/null +++ b/tests/ui/manual_option_as_slice.stderr @@ -0,0 +1,58 @@ +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:5:9 + | +LL | _ = match x.as_ref() { + | _________^ +LL | | +LL | | Some(f) => std::slice::from_ref(f), +LL | | None => &[], +LL | | }; + | |_____^ help: use: `x.as_slice()` + | + = note: `-D clippy::manual-option-as-slice` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_option_as_slice)]` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:11:9 + | +LL | _ = if let Some(f) = x.as_ref() { + | _________^ +LL | | +LL | | std::slice::from_ref(f) +LL | | } else { +LL | | &[] +LL | | }; + | |_____^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:18:9 + | +LL | _ = x.as_ref().map_or(&[][..], std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:21:9 + | +LL | _ = x.as_ref().map_or_else(Default::default, std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:24:9 + | +LL | _ = x.as_ref().map(std::slice::from_ref).unwrap_or_default(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:27:9 + | +LL | _ = x.as_ref().map_or_else(|| &[42][..0], std::slice::from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: use `Option::as_slice` + --> tests/ui/manual_option_as_slice.rs:32:13 + | +LL | _ = x.as_ref().map_or_else(<&[_]>::default, from_ref); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `x.as_slice()` + +error: aborting due to 7 previous errors + From 885278b9eb57956f8193e0dc7780e4c0d76f1cc9 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 27 Jan 2025 19:28:12 +0100 Subject: [PATCH 12/57] Stabilize `HashMap::get_many_mut` as `HashMap::get_disjoint_mut` as well as `HashMap::get_many_unchecked_mut` to `HashMap::get_disjoint_unchecked_mut`. --- library/std/src/collections/hash/map.rs | 32 +++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/library/std/src/collections/hash/map.rs b/library/std/src/collections/hash/map.rs index d2342d8fd517..5013826cb9b3 100644 --- a/library/std/src/collections/hash/map.rs +++ b/library/std/src/collections/hash/map.rs @@ -969,7 +969,6 @@ where /// # Examples /// /// ``` - /// #![feature(map_many_mut)] /// use std::collections::HashMap; /// /// let mut libraries = HashMap::new(); @@ -979,13 +978,13 @@ where /// libraries.insert("Library of Congress".to_string(), 1800); /// /// // Get Athenæum and Bodleian Library - /// let [Some(a), Some(b)] = libraries.get_many_mut([ + /// let [Some(a), Some(b)] = libraries.get_disjoint_mut([ /// "Athenæum", /// "Bodleian Library", /// ]) else { panic!() }; /// /// // Assert values of Athenæum and Library of Congress - /// let got = libraries.get_many_mut([ + /// let got = libraries.get_disjoint_mut([ /// "Athenæum", /// "Library of Congress", /// ]); @@ -998,7 +997,7 @@ where /// ); /// /// // Missing keys result in None - /// let got = libraries.get_many_mut([ + /// let got = libraries.get_disjoint_mut([ /// "Athenæum", /// "New York Public Library", /// ]); @@ -1012,21 +1011,24 @@ where /// ``` /// /// ```should_panic - /// #![feature(map_many_mut)] /// use std::collections::HashMap; /// /// let mut libraries = HashMap::new(); /// libraries.insert("Athenæum".to_string(), 1807); /// /// // Duplicate keys panic! - /// let got = libraries.get_many_mut([ + /// let got = libraries.get_disjoint_mut([ /// "Athenæum", /// "Athenæum", /// ]); /// ``` #[inline] - #[unstable(feature = "map_many_mut", issue = "97601")] - pub fn get_many_mut(&mut self, ks: [&Q; N]) -> [Option<&'_ mut V>; N] + #[doc(alias = "get_many_mut")] + #[stable(feature = "map_many_mut", since = "CURRENT_RUSTC_VERSION")] + pub fn get_disjoint_mut( + &mut self, + ks: [&Q; N], + ) -> [Option<&'_ mut V>; N] where K: Borrow, Q: Hash + Eq, @@ -1040,7 +1042,7 @@ where /// Returns an array of length `N` with the results of each query. `None` will be used if /// the key is missing. /// - /// For a safe alternative see [`get_many_mut`](`HashMap::get_many_mut`). + /// For a safe alternative see [`get_disjoint_mut`](`HashMap::get_disjoint_mut`). /// /// # Safety /// @@ -1052,7 +1054,6 @@ where /// # Examples /// /// ``` - /// #![feature(map_many_mut)] /// use std::collections::HashMap; /// /// let mut libraries = HashMap::new(); @@ -1062,13 +1063,13 @@ where /// libraries.insert("Library of Congress".to_string(), 1800); /// /// // SAFETY: The keys do not overlap. - /// let [Some(a), Some(b)] = (unsafe { libraries.get_many_unchecked_mut([ + /// let [Some(a), Some(b)] = (unsafe { libraries.get_disjoint_unchecked_mut([ /// "Athenæum", /// "Bodleian Library", /// ]) }) else { panic!() }; /// /// // SAFETY: The keys do not overlap. - /// let got = unsafe { libraries.get_many_unchecked_mut([ + /// let got = unsafe { libraries.get_disjoint_unchecked_mut([ /// "Athenæum", /// "Library of Congress", /// ]) }; @@ -1081,7 +1082,7 @@ where /// ); /// /// // SAFETY: The keys do not overlap. - /// let got = unsafe { libraries.get_many_unchecked_mut([ + /// let got = unsafe { libraries.get_disjoint_unchecked_mut([ /// "Athenæum", /// "New York Public Library", /// ]) }; @@ -1089,8 +1090,9 @@ where /// assert_eq!(got, [Some(&mut 1807), None]); /// ``` #[inline] - #[unstable(feature = "map_many_mut", issue = "97601")] - pub unsafe fn get_many_unchecked_mut( + #[doc(alias = "get_many_unchecked_mut")] + #[stable(feature = "map_many_mut", since = "CURRENT_RUSTC_VERSION")] + pub unsafe fn get_disjoint_unchecked_mut( &mut self, ks: [&Q; N], ) -> [Option<&'_ mut V>; N] From 6b7b5475f5ed7e980e952db8a9019e7f6b19c8a3 Mon Sep 17 00:00:00 2001 From: Urgau Date: Mon, 27 Jan 2025 19:32:22 +0100 Subject: [PATCH 13/57] Adjust compiler for `HashMap::get_many_mut` stabilization --- compiler/rustc_session/src/config/cfg.rs | 2 +- compiler/rustc_session/src/lib.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index d586f913335e..4f2c968637f1 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -421,7 +421,7 @@ impl CheckCfg { Some(values_target_os), Some(values_target_pointer_width), Some(values_target_vendor), - ] = self.expecteds.get_many_mut(VALUES) + ] = self.expecteds.get_disjoint_mut(VALUES) else { panic!("unable to get all the check-cfg values buckets"); }; diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs index dcf86d1a4089..b388f5eff256 100644 --- a/compiler/rustc_session/src/lib.rs +++ b/compiler/rustc_session/src/lib.rs @@ -2,7 +2,6 @@ #![allow(internal_features)] #![feature(iter_intersperse)] #![feature(let_chains)] -#![feature(map_many_mut)] #![feature(rustc_attrs)] #![warn(unreachable_pub)] // tidy-alphabetical-end From 9da9ddb7db5b3005b38386bed2bf77932e7d4b4c Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Tue, 28 Jan 2025 19:22:55 +0100 Subject: [PATCH 14/57] Merge commit '51d49c1ae2785b24ef18a46ef233fc1d91844666' into clippy-subtree-update --- .github/deploy.sh | 2 + .github/driver.sh | 6 +- .github/workflows/clippy_changelog.yml | 59 ++++ .github/workflows/clippy_mq.yml | 34 +- .github/workflows/lintcheck.yml | 8 +- .github/workflows/remark.yml | 2 +- CHANGELOG.md | 8 + CONTRIBUTING.md | 6 +- Cargo.toml | 2 +- README.md | 6 +- book/book.toml | 2 +- book/src/development/adding_lints.md | 13 +- .../development/common_tools_writing_lints.md | 4 +- book/src/development/defining_lints.md | 4 +- .../src/development/infrastructure/release.md | 4 +- book/src/lint_configuration.md | 3 + clippy_config/Cargo.toml | 2 +- clippy_config/src/conf.rs | 3 + clippy_dev/Cargo.toml | 2 +- clippy_dev/src/setup/intellij.rs | 2 +- clippy_dev/src/update_lints.rs | 2 +- clippy_dummy/Cargo.toml | 2 +- clippy_lints/Cargo.toml | 2 +- .../src/arbitrary_source_item_ordering.rs | 4 +- clippy_lints/src/assigning_clones.rs | 2 +- .../src/attrs/mixed_attributes_style.rs | 2 +- clippy_lints/src/cargo/mod.rs | 9 + clippy_lints/src/casts/cast_possible_wrap.rs | 2 +- clippy_lints/src/casts/cast_sign_loss.rs | 4 +- clippy_lints/src/checked_conversions.rs | 2 +- clippy_lints/src/declared_lints.rs | 8 + .../src/default_constructed_unit_structs.rs | 2 +- clippy_lints/src/default_numeric_fallback.rs | 22 +- clippy_lints/src/dereference.rs | 2 +- clippy_lints/src/doc/lazy_continuation.rs | 110 ++++--- clippy_lints/src/doc/mod.rs | 34 ++ clippy_lints/src/enum_clike.rs | 2 +- clippy_lints/src/eta_reduction.rs | 31 +- .../src/extra_unused_type_parameters.rs | 2 +- .../src/functions/impl_trait_in_params.rs | 2 +- clippy_lints/src/functions/must_use.rs | 2 +- clippy_lints/src/implicit_saturating_add.rs | 2 +- clippy_lints/src/implicit_saturating_sub.rs | 2 +- clippy_lints/src/iter_over_hash_type.rs | 2 +- clippy_lints/src/let_with_type_underscore.rs | 2 +- clippy_lints/src/lib.rs | 8 +- clippy_lints/src/lifetimes.rs | 93 +++++- clippy_lints/src/literal_representation.rs | 2 +- .../literal_string_with_formatting_args.rs | 4 +- clippy_lints/src/loops/mod.rs | 2 +- clippy_lints/src/loops/same_item_push.rs | 34 +- clippy_lints/src/manual_bits.rs | 4 +- clippy_lints/src/manual_div_ceil.rs | 36 +++ clippy_lints/src/manual_is_ascii_check.rs | 12 +- clippy_lints/src/manual_let_else.rs | 4 +- clippy_lints/src/manual_rem_euclid.rs | 2 +- clippy_lints/src/manual_strip.rs | 2 +- clippy_lints/src/matches/manual_filter.rs | 8 +- clippy_lints/src/matches/manual_ok_err.rs | 135 ++++++++ clippy_lints/src/matches/manual_utils.rs | 2 +- clippy_lints/src/matches/match_bool.rs | 53 +-- .../src/matches/match_like_matches.rs | 2 +- .../src/matches/match_str_case_mismatch.rs | 2 +- clippy_lints/src/matches/match_wild_enum.rs | 2 +- clippy_lints/src/matches/mod.rs | 45 +++ .../matches/significant_drop_in_scrutinee.rs | 9 +- clippy_lints/src/matches/try_err.rs | 2 +- clippy_lints/src/matches/wild_in_or_pats.rs | 2 +- clippy_lints/src/methods/bytecount.rs | 2 +- .../src/methods/bytes_count_to_len.rs | 2 +- clippy_lints/src/methods/bytes_nth.rs | 2 +- .../src/methods/cloned_instead_of_copied.rs | 2 +- clippy_lints/src/methods/drain_collect.rs | 7 +- clippy_lints/src/methods/err_expect.rs | 2 +- clippy_lints/src/methods/expect_fun_call.rs | 2 +- clippy_lints/src/methods/iter_with_drain.rs | 2 +- clippy_lints/src/methods/manual_repeat_n.rs | 43 +++ .../map_with_unused_argument_over_ranges.rs | 5 +- clippy_lints/src/methods/mod.rs | 105 +++++- clippy_lints/src/methods/needless_as_bytes.rs | 10 +- .../src/methods/obfuscated_if_else.rs | 31 +- .../src/methods/path_ends_with_ext.rs | 2 +- .../src/methods/sliced_string_as_bytes.rs | 29 ++ .../src/methods/unnecessary_iter_cloned.rs | 2 +- .../src/methods/unnecessary_map_or.rs | 37 +-- .../src/methods/unnecessary_sort_by.rs | 3 +- .../src/methods/unnecessary_to_owned.rs | 3 + .../methods/useless_nonzero_new_unchecked.rs | 59 ++++ clippy_lints/src/misc.rs | 23 +- .../src/mismatching_type_param_order.rs | 2 +- clippy_lints/src/missing_doc.rs | 2 +- clippy_lints/src/missing_fields_in_debug.rs | 2 +- clippy_lints/src/missing_inline.rs | 2 +- clippy_lints/src/multi_assignments.rs | 4 +- .../src/multiple_unsafe_ops_per_block.rs | 2 +- clippy_lints/src/mutex_atomic.rs | 2 +- clippy_lints/src/needless_late_init.rs | 6 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 4 +- clippy_lints/src/non_copy_const.rs | 10 - .../src/non_octal_unix_permissions.rs | 2 +- clippy_lints/src/non_std_lazy_statics.rs | 306 ++++++++++++++++++ .../src/operators/arithmetic_side_effects.rs | 10 +- clippy_lints/src/operators/cmp_owned.rs | 2 +- .../src/operators/const_comparisons.rs | 2 +- .../src/operators/double_comparison.rs | 2 +- clippy_lints/src/operators/float_cmp.rs | 2 +- clippy_lints/src/operators/mod.rs | 4 +- .../src/operators/modulo_arithmetic.rs | 2 +- clippy_lints/src/operators/modulo_one.rs | 2 +- clippy_lints/src/partialeq_ne_impl.rs | 2 +- clippy_lints/src/precedence.rs | 2 +- clippy_lints/src/redundant_closure_call.rs | 2 +- clippy_lints/src/redundant_pub_crate.rs | 3 + .../src/redundant_type_annotations.rs | 2 +- clippy_lints/src/repeat_vec_with_capacity.rs | 8 +- clippy_lints/src/returns.rs | 25 +- clippy_lints/src/single_range_in_vec_init.rs | 4 +- clippy_lints/src/size_of_in_element_count.rs | 6 +- .../src/slow_vector_initialization.rs | 13 +- clippy_lints/src/swap.rs | 2 +- .../missing_transmute_annotations.rs | 5 +- .../transmute/transmute_int_to_non_zero.rs | 2 +- clippy_lints/src/tuple_array_conversions.rs | 2 +- clippy_lints/src/types/borrowed_box.rs | 2 +- clippy_lints/src/unnecessary_semicolon.rs | 109 +++++++ clippy_lints/src/unneeded_struct_pattern.rs | 76 +++++ clippy_lints/src/unused_io_amount.rs | 2 +- clippy_lints/src/useless_conversion.rs | 39 ++- .../internal_lints/slow_symbol_comparisons.rs | 2 +- clippy_lints/src/vec.rs | 2 +- clippy_utils/Cargo.toml | 2 +- clippy_utils/README.md | 2 +- clippy_utils/src/check_proc_macro.rs | 4 +- clippy_utils/src/consts.rs | 8 +- clippy_utils/src/higher.rs | 2 +- clippy_utils/src/lib.rs | 31 +- clippy_utils/src/macros.rs | 2 +- clippy_utils/src/mir/possible_borrower.rs | 7 +- clippy_utils/src/msrvs.rs | 6 +- clippy_utils/src/numeric_literal.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 7 +- clippy_utils/src/ty/mod.rs | 13 +- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- clippy_utils/src/visitors.rs | 5 +- lintcheck/Cargo.toml | 2 +- lintcheck/src/config.rs | 2 +- lintcheck/src/main.rs | 4 +- rust-toolchain | 2 +- rustc_tools_util/Cargo.toml | 2 +- rustfmt.toml | 4 +- src/driver.rs | 2 +- tests/compile-test.rs | 4 +- tests/missing-test-files.rs | 2 +- tests/ui-internal/custom_ice_message.stderr | 4 + tests/ui/arithmetic_side_effects.rs | 11 + tests/ui/arithmetic_side_effects.stderr | 274 +++++++++------- tests/ui/auxiliary/non-exhaustive-enum.rs | 21 ++ tests/ui/bytes_nth.fixed | 1 + tests/ui/bytes_nth.rs | 1 + tests/ui/bytes_nth.stderr | 6 +- tests/ui/crashes/ice-11230.fixed | 16 + tests/ui/crashes/ice-11230.rs | 10 + tests/ui/crashes/ice-11230.stderr | 20 ++ tests/ui/crashes/ice-11422.fixed | 2 +- tests/ui/crashes/ice-11422.rs | 2 +- tests/ui/crashes/ice-11422.stderr | 10 +- tests/ui/doc/doc_lazy_list.fixed | 1 + tests/ui/doc/doc_lazy_list.rs | 1 + tests/ui/doc/doc_lazy_list.stderr | 22 +- .../ui/doc/doc_overindented_list_items.fixed | 28 ++ tests/ui/doc/doc_overindented_list_items.rs | 28 ++ .../ui/doc/doc_overindented_list_items.stderr | 41 +++ tests/ui/drain_collect_nostd.fixed | 8 + tests/ui/drain_collect_nostd.rs | 8 + tests/ui/drain_collect_nostd.stderr | 11 + tests/ui/eta_nostd.fixed | 10 + tests/ui/eta_nostd.rs | 10 + tests/ui/eta_nostd.stderr | 11 + tests/ui/from_iter_instead_of_collect.fixed | 2 +- tests/ui/from_iter_instead_of_collect.rs | 2 +- tests/ui/implicit_hasher.fixed | 2 +- tests/ui/implicit_hasher.rs | 2 +- tests/ui/implicit_hasher.stderr | 2 +- tests/ui/manual_div_ceil.fixed | 11 + tests/ui/manual_div_ceil.rs | 11 + tests/ui/manual_div_ceil.stderr | 14 +- tests/ui/manual_div_ceil_with_feature.fixed | 11 + tests/ui/manual_div_ceil_with_feature.rs | 11 + tests/ui/manual_div_ceil_with_feature.stderr | 32 +- tests/ui/manual_ok_err.fixed | 91 ++++++ tests/ui/manual_ok_err.rs | 125 +++++++ tests/ui/manual_ok_err.stderr | 95 ++++++ tests/ui/manual_repeat_n.fixed | 30 ++ tests/ui/manual_repeat_n.rs | 30 ++ tests/ui/manual_repeat_n.stderr | 35 ++ tests/ui/manual_str_repeat.fixed | 2 +- tests/ui/manual_str_repeat.rs | 2 +- ...map_with_unused_argument_over_ranges.fixed | 4 +- .../map_with_unused_argument_over_ranges.rs | 4 +- ...th_unused_argument_over_ranges_nostd.fixed | 8 + ..._with_unused_argument_over_ranges_nostd.rs | 8 + ...h_unused_argument_over_ranges_nostd.stderr | 15 + tests/ui/match_bool.fixed | 58 ++++ tests/ui/match_bool.rs | 54 +++- tests/ui/match_bool.stderr | 108 +++++-- .../ui/missing_const_for_fn/cant_be_const.rs | 6 + .../missing_const_for_fn/could_be_const.fixed | 5 + .../ui/missing_const_for_fn/could_be_const.rs | 5 + .../could_be_const.stderr | 16 +- tests/ui/needless_as_bytes.fixed | 36 +++ tests/ui/needless_as_bytes.rs | 36 +++ tests/ui/needless_as_bytes.stderr | 42 ++- tests/ui/needless_late_init.fixed | 11 + tests/ui/needless_late_init.rs | 11 + tests/ui/needless_lifetimes.fixed | 109 ++++++- tests/ui/needless_lifetimes.rs | 101 +++++- tests/ui/needless_lifetimes.stderr | 205 +++++++++--- .../auxiliary/lazy_static.rs | 20 ++ .../auxiliary/once_cell.rs | 55 ++++ .../non_std_lazy_static_fixable.fixed | 72 +++++ .../non_std_lazy_static_fixable.rs | 72 +++++ .../non_std_lazy_static_fixable.stderr | 100 ++++++ .../non_std_lazy_static_no_std.rs | 20 ++ .../non_std_lazy_static_other_once_cell.rs | 12 + .../non_std_lazy_static_unfixable.rs | 43 +++ .../non_std_lazy_static_unfixable.stderr | 66 ++++ tests/ui/obfuscated_if_else.fixed | 13 + tests/ui/obfuscated_if_else.rs | 13 + tests/ui/obfuscated_if_else.stderr | 36 ++- tests/ui/precedence.stderr | 22 +- tests/ui/redundant_pub_crate.fixed | 7 + tests/ui/redundant_pub_crate.rs | 7 + tests/ui/redundant_pub_crate.stderr | 32 +- tests/ui/repeat_vec_with_capacity_nostd.fixed | 10 + tests/ui/repeat_vec_with_capacity_nostd.rs | 10 + .../ui/repeat_vec_with_capacity_nostd.stderr | 16 + tests/ui/same_item_push.rs | 20 +- tests/ui/same_item_push.stderr | 36 ++- tests/ui/short_circuit_statement.fixed | 36 +++ tests/ui/short_circuit_statement.rs | 36 +++ tests/ui/short_circuit_statement.stderr | 30 +- tests/ui/significant_drop_in_scrutinee.rs | 32 ++ tests/ui/significant_drop_in_scrutinee.stderr | 34 +- tests/ui/sliced_string_as_bytes.fixed | 34 ++ tests/ui/sliced_string_as_bytes.rs | 34 ++ tests/ui/sliced_string_as_bytes.stderr | 23 ++ tests/ui/slow_vector_initialization.fixed | 86 +++++ tests/ui/slow_vector_initialization.rs | 3 +- tests/ui/slow_vector_initialization.stderr | 26 +- tests/ui/unnecessary_map_or.fixed | 17 + tests/ui/unnecessary_map_or.rs | 17 + tests/ui/unnecessary_map_or.stderr | 194 +++++++++-- .../unnecessary_semicolon.edition2021.fixed | 57 ++++ .../unnecessary_semicolon.edition2021.stderr | 23 ++ .../unnecessary_semicolon.edition2024.fixed | 57 ++++ .../unnecessary_semicolon.edition2024.stderr | 29 ++ tests/ui/unnecessary_semicolon.fixed | 32 ++ tests/ui/unnecessary_semicolon.rs | 57 ++++ tests/ui/unnecessary_semicolon.stderr | 17 + tests/ui/unnecessary_to_owned.fixed | 6 + tests/ui/unnecessary_to_owned.rs | 6 + tests/ui/unneeded_struct_pattern.fixed | 177 ++++++++++ tests/ui/unneeded_struct_pattern.rs | 177 ++++++++++ tests/ui/unneeded_struct_pattern.stderr | 203 ++++++++++++ tests/ui/useless_conversion.fixed | 53 +++ tests/ui/useless_conversion.rs | 53 +++ tests/ui/useless_conversion.stderr | 123 ++++++- tests/ui/useless_nonzero_new_unchecked.fixed | 52 +++ tests/ui/useless_nonzero_new_unchecked.rs | 52 +++ tests/ui/useless_nonzero_new_unchecked.stderr | 37 +++ tests/versioncheck.rs | 2 +- triagebot.toml | 1 - util/gh-pages/script.js | 4 +- 273 files changed, 5679 insertions(+), 795 deletions(-) create mode 100644 .github/workflows/clippy_changelog.yml create mode 100644 clippy_lints/src/matches/manual_ok_err.rs create mode 100644 clippy_lints/src/methods/manual_repeat_n.rs create mode 100644 clippy_lints/src/methods/sliced_string_as_bytes.rs create mode 100644 clippy_lints/src/methods/useless_nonzero_new_unchecked.rs create mode 100644 clippy_lints/src/non_std_lazy_statics.rs create mode 100644 clippy_lints/src/unnecessary_semicolon.rs create mode 100644 clippy_lints/src/unneeded_struct_pattern.rs create mode 100644 tests/ui/crashes/ice-11230.fixed create mode 100644 tests/ui/crashes/ice-11230.stderr create mode 100644 tests/ui/doc/doc_overindented_list_items.fixed create mode 100644 tests/ui/doc/doc_overindented_list_items.rs create mode 100644 tests/ui/doc/doc_overindented_list_items.stderr create mode 100644 tests/ui/drain_collect_nostd.fixed create mode 100644 tests/ui/drain_collect_nostd.rs create mode 100644 tests/ui/drain_collect_nostd.stderr create mode 100644 tests/ui/eta_nostd.fixed create mode 100644 tests/ui/eta_nostd.rs create mode 100644 tests/ui/eta_nostd.stderr create mode 100644 tests/ui/manual_ok_err.fixed create mode 100644 tests/ui/manual_ok_err.rs create mode 100644 tests/ui/manual_ok_err.stderr create mode 100644 tests/ui/manual_repeat_n.fixed create mode 100644 tests/ui/manual_repeat_n.rs create mode 100644 tests/ui/manual_repeat_n.stderr create mode 100644 tests/ui/map_with_unused_argument_over_ranges_nostd.fixed create mode 100644 tests/ui/map_with_unused_argument_over_ranges_nostd.rs create mode 100644 tests/ui/map_with_unused_argument_over_ranges_nostd.stderr create mode 100644 tests/ui/match_bool.fixed create mode 100644 tests/ui/non_std_lazy_static/auxiliary/lazy_static.rs create mode 100644 tests/ui/non_std_lazy_static/auxiliary/once_cell.rs create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.fixed create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.stderr create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_no_std.rs create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_other_once_cell.rs create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs create mode 100644 tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.stderr create mode 100644 tests/ui/repeat_vec_with_capacity_nostd.fixed create mode 100644 tests/ui/repeat_vec_with_capacity_nostd.rs create mode 100644 tests/ui/repeat_vec_with_capacity_nostd.stderr create mode 100644 tests/ui/sliced_string_as_bytes.fixed create mode 100644 tests/ui/sliced_string_as_bytes.rs create mode 100644 tests/ui/sliced_string_as_bytes.stderr create mode 100644 tests/ui/slow_vector_initialization.fixed create mode 100644 tests/ui/unnecessary_semicolon.edition2021.fixed create mode 100644 tests/ui/unnecessary_semicolon.edition2021.stderr create mode 100644 tests/ui/unnecessary_semicolon.edition2024.fixed create mode 100644 tests/ui/unnecessary_semicolon.edition2024.stderr create mode 100644 tests/ui/unnecessary_semicolon.fixed create mode 100644 tests/ui/unnecessary_semicolon.rs create mode 100644 tests/ui/unnecessary_semicolon.stderr create mode 100644 tests/ui/unneeded_struct_pattern.fixed create mode 100644 tests/ui/unneeded_struct_pattern.rs create mode 100644 tests/ui/unneeded_struct_pattern.stderr create mode 100644 tests/ui/useless_nonzero_new_unchecked.fixed create mode 100644 tests/ui/useless_nonzero_new_unchecked.rs create mode 100644 tests/ui/useless_nonzero_new_unchecked.stderr diff --git a/.github/deploy.sh b/.github/deploy.sh index ea118a3b6fce..2f062799a360 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -45,6 +45,8 @@ if [[ -n $TAG_NAME ]]; then git add "$TAG_NAME" # Update the symlink git add stable + # Update the index.html file + git add index.html git commit -m "Add documentation for ${TAG_NAME} release: ${SHA}" elif [[ $BETA = "true" ]]; then if git diff --exit-code --quiet -- beta/; then diff --git a/.github/driver.sh b/.github/driver.sh index 701be6bd76d3..5a81b4112918 100755 --- a/.github/driver.sh +++ b/.github/driver.sh @@ -47,9 +47,9 @@ unset CARGO_MANIFEST_DIR # Run a lint and make sure it produces the expected output. It's also expected to exit with code 1 # FIXME: How to match the clippy invocation in compile-test.rs? -./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/box_default.rs 2>box_default.stderr && exit 1 -sed -e "/= help: for/d" box_default.stderr > normalized.stderr -diff -u normalized.stderr tests/ui/box_default.stderr +./target/debug/clippy-driver -Dwarnings -Aunused -Zui-testing --emit metadata --crate-type bin tests/ui/string_to_string.rs 2>string_to_string.stderr && exit 1 +sed -e "/= help: for/d" string_to_string.stderr > normalized.stderr +diff -u normalized.stderr tests/ui/string_to_string.stderr # make sure "clippy-driver --rustc --arg" and "rustc --arg" behave the same SYSROOT=$(rustc --print sysroot) diff --git a/.github/workflows/clippy_changelog.yml b/.github/workflows/clippy_changelog.yml new file mode 100644 index 000000000000..a2657bfea490 --- /dev/null +++ b/.github/workflows/clippy_changelog.yml @@ -0,0 +1,59 @@ +name: Clippy changelog check + +on: + merge_group: + pull_request: + types: [opened, reopened, synchronize, edited] + +concurrency: + # For a given workflow, if we push to the same PR, cancel all previous builds on that PR. + # If the push is not attached to a PR, we will cancel all builds on the same branch. + group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" + cancel-in-progress: true + +jobs: + changelog: + runs-on: ubuntu-latest + + defaults: + run: + shell: bash + + steps: + # Run + - name: Check Changelog + if: ${{ github.event_name == 'pull_request' }} + run: | + body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR_NUMBER" | \ + python -c "import sys, json; print(json.load(sys.stdin)['body'])") + output=$(awk '/^changelog:\s*\S/ && !/changelog: \[.*\]: your change/' <<< "$body" | sed "s/changelog:\s*//g") + if [ -z "$output" ]; then + echo "ERROR: pull request message must contain 'changelog: ...' with your changelog. Please add it." + exit 1 + else + echo "changelog: $output" + fi + env: + PYTHONIOENCODING: 'utf-8' + PR_NUMBER: '${{ github.event.number }}' + + # We need to have the "conclusion" job also on PR CI, to make it possible + # to add PRs to a merge queue. + conclusion_changelog: + needs: [ changelog ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + steps: + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index dee7d028655e..c337a96bdac5 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -15,37 +15,7 @@ defaults: shell: bash jobs: - changelog: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - # Unsetting this would make so that any malicious package could get our Github Token - persist-credentials: false - - # Run - - name: Check Changelog - run: | - MESSAGE=$(git log --format=%B -n 1) - PR=$(echo "$MESSAGE" | grep -o "#[0-9]*" | head -1 | sed -e 's/^#//') - body=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s "https://api.github.com/repos/rust-lang/rust-clippy/pulls/$PR" | \ - python -c "import sys, json; print(json.load(sys.stdin)['body'])") - output=$(grep "^changelog:\s*\S" <<< "$body" | sed "s/changelog:\s*//g") || { - echo "ERROR: PR body must contain 'changelog: ...'" - exit 1 - } - if [[ "$output" = "none" ]]; then - echo "WARNING: changelog is 'none'" - else - echo "changelog: $output" - fi - env: - PYTHONIOENCODING: 'utf-8' base: - needs: changelog strategy: matrix: include: @@ -119,7 +89,6 @@ jobs: OS: ${{ runner.os }} metadata_collection: - needs: changelog runs-on: ubuntu-latest steps: @@ -138,7 +107,6 @@ jobs: run: cargo collect-metadata integration_build: - needs: changelog runs-on: ubuntu-latest steps: @@ -228,7 +196,7 @@ jobs: INTEGRATION: ${{ matrix.integration }} conclusion: - needs: [ changelog, base, metadata_collection, integration_build, integration ] + needs: [ base, metadata_collection, integration_build, integration ] # We need to ensure this job does *not* get skipped if its dependencies fail, # because a skipped job is considered a success by GitHub. So we have to # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run diff --git a/.github/workflows/lintcheck.yml b/.github/workflows/lintcheck.yml index 64966f1d1898..d487c7d94985 100644 --- a/.github/workflows/lintcheck.yml +++ b/.github/workflows/lintcheck.yml @@ -1,6 +1,12 @@ name: Lintcheck -on: pull_request +on: + pull_request: + paths-ignore: + - 'book/**' + - 'util/**' + - 'tests/**' + - '*.md' env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 69d00dc027e8..13902f78b541 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -27,7 +27,7 @@ jobs: - name: Install mdbook run: | mkdir mdbook - curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.34/mdbook-v0.4.34-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.43/mdbook-v0.4.43-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook echo `pwd`/mdbook >> $GITHUB_PATH # Run diff --git a/CHANGELOG.md b/CHANGELOG.md index 1770e8095a01..bc42c07224e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5533,6 +5533,7 @@ Released 2018-09-13 [`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs +[`doc_overindented_list_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_overindented_list_items [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons [`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last [`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use @@ -5762,11 +5763,13 @@ Released 2018-09-13 [`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy [`manual_next_back`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_next_back [`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive +[`manual_ok_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_err [`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or [`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_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_repeat_n`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n [`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 @@ -5904,6 +5907,7 @@ Released 2018-09-13 [`non_minimal_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_minimal_cfg [`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions [`non_send_fields_in_send_ty`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_send_fields_in_send_ty +[`non_std_lazy_statics`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics [`non_zero_suggestions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_zero_suggestions [`nonminimal_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonminimal_bool [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options @@ -6063,6 +6067,7 @@ Released 2018-09-13 [`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count [`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref [`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next +[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes [`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization [`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive [`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc @@ -6172,12 +6177,14 @@ Released 2018-09-13 [`unnecessary_safety_comment`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_comment [`unnecessary_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_safety_doc [`unnecessary_self_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_self_imports +[`unnecessary_semicolon`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_semicolon [`unnecessary_sort_by`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_sort_by [`unnecessary_struct_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_struct_initialization [`unnecessary_to_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_to_owned [`unnecessary_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap [`unnecessary_wraps`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps [`unneeded_field_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_field_pattern +[`unneeded_struct_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_struct_pattern [`unneeded_wildcard_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#unneeded_wildcard_pattern [`unnested_or_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns [`unreachable`]: https://rust-lang.github.io/rust-clippy/master/index.html#unreachable @@ -6216,6 +6223,7 @@ Released 2018-09-13 [`useless_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_conversion [`useless_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_format [`useless_let_if_seq`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_let_if_seq +[`useless_nonzero_new_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_nonzero_new_unchecked [`useless_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_transmute [`useless_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#useless_vec [`vec_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#vec_box diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f6c918fc6cc..3b33f7190638 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -199,7 +199,7 @@ currently. Between writing new lints, fixing issues, reviewing pull requests and responding to issues there may not always be enough time to stay on top of it all. -Our highest priority is fixing [crashes][l-crash] and [bugs][l-bug], for example +Our highest priority is fixing [ICEs][I-ICE] and [bugs][C-bug], for example an ICE in a popular crate that many other crates depend on. We don't want Clippy to crash on your code and we want it to be as reliable as the suggestions from Rust compiler errors. @@ -213,8 +213,8 @@ Or rather: before the sync this should be addressed, e.g. by removing a lint again, so it doesn't hit beta/stable. [triage]: https://forge.rust-lang.org/release/triage-procedure.html -[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash -[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug +[I-ICE]: https://github.com/rust-lang/rust-clippy/labels/I-ICE +[C-bug]: https://github.com/rust-lang/rust-clippy/labels/C-bug [p-low]: https://github.com/rust-lang/rust-clippy/labels/P-low [p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium [p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high diff --git a/Cargo.toml b/Cargo.toml index efa59fecc0e8..c9e4f15afbf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" keywords = ["clippy", "lint", "plugin"] categories = ["development-tools", "development-tools::cargo-plugins"] build = "build.rs" -edition = "2021" +edition = "2024" publish = false [[bin]] diff --git a/README.md b/README.md index cb3a22d4288f..32c1d33e2ed3 100644 --- a/README.md +++ b/README.md @@ -159,11 +159,11 @@ line. (You can swap `clippy::all` with the specific lint category you are target You can add options to your code to `allow`/`warn`/`deny` Clippy lints: * the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`). - Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). + Note that `rustc` has additional [lint groups](https://doc.rust-lang.org/rustc/lints/groups.html). * all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, - `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive - lints prone to false positives. + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive + lints prone to false positives. * only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) diff --git a/book/book.toml b/book/book.toml index 93b6641f7e1e..c918aadf83c4 100644 --- a/book/book.toml +++ b/book/book.toml @@ -6,7 +6,7 @@ src = "src" title = "Clippy Documentation" [rust] -edition = "2018" +edition = "2024" [output.html] edit-url-template = "https://github.com/rust-lang/rust-clippy/edit/master/book/{path}" diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index c07568697d02..c26ad319f4f5 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -299,10 +299,11 @@ This is good, because it makes writing this particular lint less complicated. We have to make this decision with every new Clippy lint. It boils down to using either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. -In short, the `EarlyLintPass` runs before type checking and -[HIR](https://rustc-dev-guide.rust-lang.org/hir.html) lowering and the `LateLintPass` -has access to type information. Consider using the `LateLintPass` unless you need -something specific from the `EarlyLintPass`. +`EarlyLintPass` runs before type checking and +[HIR](https://rustc-dev-guide.rust-lang.org/hir.html) lowering, while `LateLintPass` +runs after these stages, providing access to type information. The `cargo dev new_lint` command +defaults to the recommended `LateLintPass`, but you can specify `--pass=early` if your lint +only needs AST level analysis. Since we don't need type information for checking the function name, we used `--pass=early` when running the new lint automation and all the imports were @@ -537,7 +538,7 @@ via `Tools -> Clippy` and you should see the generated code in the output below. If the command was executed successfully, you can copy the code over to where you are implementing your lint. -[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 +[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 ## Print HIR lint @@ -552,7 +553,7 @@ attribute to expressions you often need to enable _Clippy_. [_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html -[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb +[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=daf14db3a7f39ca467cd1b86c34b9afb ## Documentation diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index c354e8914f5d..b44ad80a25cc 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -265,10 +265,10 @@ functions to deal with macros: ``` [Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html -[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_type_ir/ty_kind/enum.TyKind.html [TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html [expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty [LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html [TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html -[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.pat_ty [paths]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/paths/index.html diff --git a/book/src/development/defining_lints.md b/book/src/development/defining_lints.md index ceabb255e2d0..169cecd7d11d 100644 --- a/book/src/development/defining_lints.md +++ b/book/src/development/defining_lints.md @@ -139,10 +139,10 @@ Untracked files: ``` -## The `define_clippy_lints` macro +## The `declare_clippy_lint` macro After `cargo dev new_lint`, you should see a macro with the name -`define_clippy_lints`. It will be in the same file if you defined a standalone +`declare_clippy_lint`. It will be in the same file if you defined a standalone lint, and it will be in `mod.rs` if you defined a type-specific lint. The macro looks something like this: diff --git a/book/src/development/infrastructure/release.md b/book/src/development/infrastructure/release.md index 20b870eb69a8..8b080c099b81 100644 --- a/book/src/development/infrastructure/release.md +++ b/book/src/development/infrastructure/release.md @@ -96,9 +96,9 @@ git tag rust-1.XX.0 # XX should be exchanged with the correspondin git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote ``` -After this, the release should be available on the Clippy [release page]. +After this, the release should be available on the Clippy [tags page]. -[release page]: https://github.com/rust-lang/rust-clippy/releases +[tags page]: https://github.com/rust-lang/rust-clippy/tags ## Publish `clippy_utils` diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 181e794e6e46..b8f9fff9c613 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -750,6 +750,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`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_repeat_n`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_repeat_n) * [`manual_retain`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_retain) * [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once) * [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat) @@ -762,11 +763,13 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default) * [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn) * [`needless_borrow`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow) +* [`non_std_lazy_statics`](https://rust-lang.github.io/rust-clippy/master/index.html#non_std_lazy_statics) * [`option_as_ref_deref`](https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref) * [`option_map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unwrap_or) * [`ptr_as_ptr`](https://rust-lang.github.io/rust-clippy/master/index.html#ptr_as_ptr) * [`redundant_field_names`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_field_names) * [`redundant_static_lifetimes`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_static_lifetimes) +* [`same_item_push`](https://rust-lang.github.io/rust-clippy/master/index.html#same_item_push) * [`seek_from_current`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_from_current) * [`seek_rewind`](https://rust-lang.github.io/rust-clippy/master/index.html#seek_rewind) * [`transmute_ptr_to_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#transmute_ptr_to_ref) diff --git a/clippy_config/Cargo.toml b/clippy_config/Cargo.toml index c761e207c6b6..e473a5839402 100644 --- a/clippy_config/Cargo.toml +++ b/clippy_config/Cargo.toml @@ -3,7 +3,7 @@ name = "clippy_config" # begin autogenerated version version = "0.1.86" # end autogenerated version -edition = "2021" +edition = "2024" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index c616589c56e0..552141476f3a 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -619,6 +619,7 @@ define_Conf! { manual_pattern_char_comparison, manual_range_contains, manual_rem_euclid, + manual_repeat_n, manual_retain, manual_split_once, manual_str_repeat, @@ -631,11 +632,13 @@ define_Conf! { mem_replace_with_default, missing_const_for_fn, needless_borrow, + non_std_lazy_statics, option_as_ref_deref, option_map_unwrap_or, ptr_as_ptr, redundant_field_names, redundant_static_lifetimes, + same_item_push, seek_from_current, seek_rewind, transmute_ptr_to_ref, diff --git a/clippy_dev/Cargo.toml b/clippy_dev/Cargo.toml index d3a103eaf4c6..47b7b3758613 100644 --- a/clippy_dev/Cargo.toml +++ b/clippy_dev/Cargo.toml @@ -2,7 +2,7 @@ name = "clippy_dev" description = "Clippy developer tooling" version = "0.0.1" -edition = "2021" +edition = "2024" [dependencies] aho-corasick = "1.0" diff --git a/clippy_dev/src/setup/intellij.rs b/clippy_dev/src/setup/intellij.rs index a7138f36a4ef..c56811ee0a01 100644 --- a/clippy_dev/src/setup/intellij.rs +++ b/clippy_dev/src/setup/intellij.rs @@ -62,7 +62,7 @@ fn check_and_get_rustc_dir(rustc_path: &str) -> Result { eprintln!("error: unable to get the absolute path of rustc ({err})"); return Err(()); }, - }; + } } let path = path.join("compiler"); diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 612d1c0ae139..fc0780f89a7f 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -842,7 +842,7 @@ fn try_rename_file(old_name: &Path, new_name: &Path) -> bool { Ok(file) => drop(file), Err(e) if matches!(e.kind(), io::ErrorKind::AlreadyExists | io::ErrorKind::NotFound) => return false, Err(e) => panic_file(e, new_name, "create"), - }; + } match fs::rename(old_name, new_name) { Ok(()) => true, Err(e) => { diff --git a/clippy_dummy/Cargo.toml b/clippy_dummy/Cargo.toml index c206a1eb07b5..61bdd421c764 100644 --- a/clippy_dummy/Cargo.toml +++ b/clippy_dummy/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clippy_dummy" # rename to clippy before publishing version = "0.0.303" -edition = "2018" +edition = "2024" readme = "crates-readme.md" description = "A bunch of helpful lints to avoid common pitfalls in Rust." build = 'build.rs' diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index b575ac1bf4cc..c62a7ec783b6 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" license = "MIT OR Apache-2.0" keywords = ["clippy", "lint", "plugin"] -edition = "2021" +edition = "2024" [dependencies] arrayvec = { version = "0.7", default-features = false } diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs index 380b094d017e..0389223c3e0f 100644 --- a/clippy_lints/src/arbitrary_source_item_ordering.rs +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -445,8 +445,8 @@ fn convert_assoc_item_kind(value: AssocItemKind) -> SourceItemOrderingTraitAssoc #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. use SourceItemOrderingTraitAssocItemKind::*; match value { - AssocItemKind::Const { .. } => Const, - AssocItemKind::Type { .. } => Type, + AssocItemKind::Const => Const, + AssocItemKind::Type => Type, AssocItemKind::Fn { .. } => Fn, } } diff --git a/clippy_lints/src/assigning_clones.rs b/clippy_lints/src/assigning_clones.rs index c8dd77d9578d..c01155ca86e0 100644 --- a/clippy_lints/src/assigning_clones.rs +++ b/clippy_lints/src/assigning_clones.rs @@ -257,7 +257,7 @@ fn build_sugg<'tcx>( // 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})") }, diff --git a/clippy_lints/src/attrs/mixed_attributes_style.rs b/clippy_lints/src/attrs/mixed_attributes_style.rs index 8c91c65eaf76..3e4bcfbfc190 100644 --- a/clippy_lints/src/attrs/mixed_attributes_style.rs +++ b/clippy_lints/src/attrs/mixed_attributes_style.rs @@ -60,7 +60,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item_span: Span, attrs: &[Attribute]) } outer_attr_kind.insert(kind); }, - }; + } } } diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index 96a2b1614646..60371dcd7715 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -161,6 +161,15 @@ declare_clippy_lint! { /// [dependencies] /// regex = "*" /// ``` + /// Use instead: + /// ```toml + /// [dependencies] + /// # allow patch updates, but not minor or major version changes + /// some_crate_1 = "~1.2.3" + /// + /// # pin the version to a specific version + /// some_crate_2 = "=1.2.3" + /// ``` #[clippy::version = "1.32.0"] pub WILDCARD_DEPENDENCIES, cargo, diff --git a/clippy_lints/src/casts/cast_possible_wrap.rs b/clippy_lints/src/casts/cast_possible_wrap.rs index 3cf4a43b0d4c..504d0a267e47 100644 --- a/clippy_lints/src/casts/cast_possible_wrap.rs +++ b/clippy_lints/src/casts/cast_possible_wrap.rs @@ -84,6 +84,6 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_from: Ty<'_>, ca diag .note("`usize` and `isize` may be as small as 16 bits on some platforms") .note("for more information see https://doc.rust-lang.org/reference/types/numeric.html#machine-dependent-integer-types"); - }; + } }); } diff --git a/clippy_lints/src/casts/cast_sign_loss.rs b/clippy_lints/src/casts/cast_sign_loss.rs index 4be53ace6871..45045e58ac75 100644 --- a/clippy_lints/src/casts/cast_sign_loss.rs +++ b/clippy_lints/src/casts/cast_sign_loss.rs @@ -205,7 +205,7 @@ fn expr_muldiv_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { // - uncertain if there are any uncertain values (because they could be negative or positive), Sign::Uncertain => return Sign::Uncertain, Sign::ZeroOrPositive => (), - }; + } } // A mul/div is: @@ -236,7 +236,7 @@ fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign { // - uncertain if there are any uncertain values (because they could be negative or positive), Sign::Uncertain => return Sign::Uncertain, Sign::ZeroOrPositive => positive_count += 1, - }; + } } // A sum is: diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 364f5c7dc7a0..1edfde974227 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -273,7 +273,7 @@ fn get_types_from_cast<'a>( }, _ => {}, } - }; + } None } diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 87e546fbf014..86bcf8edd578 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -142,6 +142,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::doc::DOC_LINK_WITH_QUOTES_INFO, crate::doc::DOC_MARKDOWN_INFO, crate::doc::DOC_NESTED_REFDEFS_INFO, + crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, crate::doc::EMPTY_DOCS_INFO, crate::doc::EMPTY_LINE_AFTER_DOC_COMMENTS_INFO, crate::doc::EMPTY_LINE_AFTER_OUTER_ATTR_INFO, @@ -335,6 +336,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::matches::INFALLIBLE_DESTRUCTURING_MATCH_INFO, crate::matches::MANUAL_FILTER_INFO, crate::matches::MANUAL_MAP_INFO, + crate::matches::MANUAL_OK_ERR_INFO, crate::matches::MANUAL_UNWRAP_OR_INFO, crate::matches::MATCH_AS_REF_INFO, crate::matches::MATCH_BOOL_INFO, @@ -419,6 +421,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_IS_VARIANT_AND_INFO, crate::methods::MANUAL_NEXT_BACK_INFO, crate::methods::MANUAL_OK_OR_INFO, + crate::methods::MANUAL_REPEAT_N_INFO, crate::methods::MANUAL_SATURATING_ARITHMETIC_INFO, crate::methods::MANUAL_SPLIT_ONCE_INFO, crate::methods::MANUAL_STR_REPEAT_INFO, @@ -466,6 +469,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO, crate::methods::SINGLE_CHAR_ADD_STR_INFO, crate::methods::SKIP_WHILE_NEXT_INFO, + crate::methods::SLICED_STRING_AS_BYTES_INFO, crate::methods::STABLE_SORT_PRIMITIVE_INFO, crate::methods::STRING_EXTEND_CHARS_INFO, crate::methods::STRING_LIT_CHARS_ANY_INFO, @@ -495,6 +499,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::UNWRAP_OR_DEFAULT_INFO, crate::methods::UNWRAP_USED_INFO, crate::methods::USELESS_ASREF_INFO, + crate::methods::USELESS_NONZERO_NEW_UNCHECKED_INFO, crate::methods::VEC_RESIZE_TO_ZERO_INFO, crate::methods::VERBOSE_FILE_READS_INFO, crate::methods::WAKER_CLONE_WAKE_INFO, @@ -572,6 +577,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::non_expressive_names::SIMILAR_NAMES_INFO, crate::non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS_INFO, crate::non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY_INFO, + crate::non_std_lazy_statics::NON_STD_LAZY_STATICS_INFO, crate::non_zero_suggestions::NON_ZERO_SUGGESTIONS_INFO, crate::nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES_INFO, crate::octal_escapes::OCTAL_ESCAPES_INFO, @@ -753,8 +759,10 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::unnecessary_map_on_constructor::UNNECESSARY_MAP_ON_CONSTRUCTOR_INFO, crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO, crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO, + crate::unnecessary_semicolon::UNNECESSARY_SEMICOLON_INFO, crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO, crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO, + crate::unneeded_struct_pattern::UNNEEDED_STRUCT_PATTERN_INFO, crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO, crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO, crate::unused_async::UNUSED_ASYNC_INFO, diff --git a/clippy_lints/src/default_constructed_unit_structs.rs b/clippy_lints/src/default_constructed_unit_structs.rs index 33a97222b8f8..bbd5dc15542d 100644 --- a/clippy_lints/src/default_constructed_unit_structs.rs +++ b/clippy_lints/src/default_constructed_unit_structs.rs @@ -80,6 +80,6 @@ impl LateLintPass<'_> for DefaultConstructedUnitStructs { String::new(), Applicability::MachineApplicable, ); - }; + } } } diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index c04a73c890f0..ca3eaae7b85f 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -223,19 +223,17 @@ impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> { } fn visit_pat(&mut self, pat: &'tcx Pat<'_>) { - match pat.kind { - PatKind::Expr(&PatExpr { - hir_id, - kind: PatExprKind::Lit { lit, .. }, - .. - }) => { - let ty = self.cx.typeck_results().node_type(hir_id); - self.check_lit(lit, ty, hir_id); - return; - }, - _ => {}, + if let PatKind::Expr(&PatExpr { + hir_id, + kind: PatExprKind::Lit { lit, .. }, + .. + }) = pat.kind + { + let ty = self.cx.typeck_results().node_type(hir_id); + self.check_lit(lit, ty, hir_id); + return; } - walk_pat(self, pat) + walk_pat(self, pat); } fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index f5589d8f8e25..123e358d7c3d 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -331,7 +331,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> { deref_count += 1; }, None => break None, - }; + } }; let use_node = use_cx.use_node(cx); diff --git a/clippy_lints/src/doc/lazy_continuation.rs b/clippy_lints/src/doc/lazy_continuation.rs index f9e4a43c0e7a..2577324f23df 100644 --- a/clippy_lints/src/doc/lazy_continuation.rs +++ b/clippy_lints/src/doc/lazy_continuation.rs @@ -1,11 +1,12 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use itertools::Itertools; use rustc_errors::Applicability; use rustc_lint::LateContext; use rustc_span::{BytePos, Span}; +use std::cmp::Ordering; use std::ops::Range; -use super::DOC_LAZY_CONTINUATION; +use super::{DOC_LAZY_CONTINUATION, DOC_OVERINDENTED_LIST_ITEMS}; fn map_container_to_text(c: &super::Container) -> &'static str { match c { @@ -28,12 +29,57 @@ pub(super) fn check( return; } + // Blockquote let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count(); let blockquote_level = containers .iter() .filter(|c| matches!(c, super::Container::Blockquote)) .count(); - let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count(); + if ccount < blockquote_level { + span_lint_and_then( + cx, + DOC_LAZY_CONTINUATION, + span, + "doc quote line without `>` marker", + |diag| { + let mut doc_start_range = &doc[range]; + let mut suggested = String::new(); + for c in containers { + let text = map_container_to_text(c); + if doc_start_range.starts_with(text) { + doc_start_range = &doc_start_range[text.len()..]; + span = span.with_lo( + span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")), + ); + } else if matches!(c, super::Container::Blockquote) + && let Some(i) = doc_start_range.find('>') + { + doc_start_range = &doc_start_range[i + 1..]; + span = span + .with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1)); + } else { + suggested.push_str(text); + } + } + diag.span_suggestion_verbose( + span, + "add markers to start of line", + suggested, + Applicability::MachineApplicable, + ); + diag.help("if this not intended to be a quote at all, escape it with `\\>`"); + }, + ); + return; + } + + if ccount != 0 && blockquote_level != 0 { + // If this doc is a blockquote, we don't go further. + return; + } + + // List + let leading_spaces = doc[range].chars().filter(|c| *c == ' ').count(); let list_indentation = containers .iter() .map(|c| { @@ -44,50 +90,36 @@ pub(super) fn check( } }) .sum(); - if ccount < blockquote_level || lcount < list_indentation { - let msg = if ccount < blockquote_level { - "doc quote line without `>` marker" - } else { - "doc list item without indentation" - }; - span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| { - if ccount == 0 && blockquote_level == 0 { + match leading_spaces.cmp(&list_indentation) { + Ordering::Less => span_lint_and_then( + cx, + DOC_LAZY_CONTINUATION, + span, + "doc list item without indentation", + |diag| { // simpler suggestion style for indentation - let indent = list_indentation - lcount; + let indent = list_indentation - leading_spaces; diag.span_suggestion_verbose( span.shrink_to_hi(), "indent this line", - std::iter::repeat(" ").take(indent).join(""), + std::iter::repeat_n(" ", indent).join(""), Applicability::MaybeIncorrect, ); diag.help("if this is supposed to be its own paragraph, add a blank line"); - return; - } - let mut doc_start_range = &doc[range]; - let mut suggested = String::new(); - for c in containers { - let text = map_container_to_text(c); - if doc_start_range.starts_with(text) { - doc_start_range = &doc_start_range[text.len()..]; - span = span - .with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger"))); - } else if matches!(c, super::Container::Blockquote) - && let Some(i) = doc_start_range.find('>') - { - doc_start_range = &doc_start_range[i + 1..]; - span = - span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1)); - } else { - suggested.push_str(text); - } - } - diag.span_suggestion_verbose( + }, + ), + Ordering::Greater => { + let sugg = std::iter::repeat_n(" ", list_indentation).join(""); + span_lint_and_sugg( + cx, + DOC_OVERINDENTED_LIST_ITEMS, span, - "add markers to start of line", - suggested, - Applicability::MachineApplicable, + "doc list item overindented", + format!("try using `{sugg}` ({list_indentation} spaces)"), + sugg, + Applicability::MaybeIncorrect, ); - diag.help("if this not intended to be a quote at all, escape it with `\\>`"); - }); + }, + Ordering::Equal => {}, } } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 7561a6cf2a78..15530c3dbc50 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -428,6 +428,39 @@ declare_clippy_lint! { "require every line of a paragraph to be indented and marked" } +declare_clippy_lint! { + /// ### What it does + /// + /// Detects overindented list items in doc comments where the continuation + /// lines are indented more than necessary. + /// + /// ### Why is this bad? + /// + /// Overindented list items in doc comments can lead to inconsistent and + /// poorly formatted documentation when rendered. Excessive indentation may + /// cause the text to be misinterpreted as a nested list item or code block, + /// affecting readability and the overall structure of the documentation. + /// + /// ### Example + /// + /// ```no_run + /// /// - This is the first item in a list + /// /// and this line is overindented. + /// # fn foo() {} + /// ``` + /// + /// Fixes this into: + /// ```no_run + /// /// - This is the first item in a list + /// /// and this line is overindented. + /// # fn foo() {} + /// ``` + #[clippy::version = "1.80.0"] + pub DOC_OVERINDENTED_LIST_ITEMS, + style, + "ensure list items are not overindented" +} + declare_clippy_lint! { /// ### What it does /// Checks if the first paragraph in the documentation of items listed in the module page is too long. @@ -617,6 +650,7 @@ impl_lint_pass!(Documentation => [ SUSPICIOUS_DOC_COMMENTS, EMPTY_DOCS, DOC_LAZY_CONTINUATION, + DOC_OVERINDENTED_LIST_ITEMS, EMPTY_LINE_AFTER_OUTER_ATTR, EMPTY_LINE_AFTER_DOC_COMMENTS, TOO_LONG_FIRST_DOC_PARAGRAPH, diff --git a/clippy_lints/src/enum_clike.rs b/clippy_lints/src/enum_clike.rs index e9e9d00907ea..a090a987d4fc 100644 --- a/clippy_lints/src/enum_clike.rs +++ b/clippy_lints/src/enum_clike.rs @@ -70,7 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for UnportableVariant { var.span, "C-like enum variant discriminant is not portable to 32-bit targets", ); - }; + } } } } diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 57a30de7ad14..52b699274bbb 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -3,7 +3,9 @@ use clippy_utils::higher::VecArgs; use clippy_utils::source::snippet_opt; use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; -use clippy_utils::{get_path_from_caller_to_method_type, is_adjusted, path_to_local, path_to_local_id}; +use clippy_utils::{ + get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, +}; use rustc_errors::Applicability; use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; @@ -101,19 +103,20 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx }; if body.value.span.from_expansion() { - if body.params.is_empty() { - if let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) { - // replace `|| vec![]` with `Vec::new` - span_lint_and_sugg( - cx, - REDUNDANT_CLOSURE, - expr.span, - "redundant closure", - "replace the closure with `Vec::new`", - "std::vec::Vec::new".into(), - Applicability::MachineApplicable, - ); - } + if body.params.is_empty() + && let Some(VecArgs::Vec(&[])) = VecArgs::hir(cx, body.value) + { + let vec_crate = if is_no_std_crate(cx) { "alloc" } else { "std" }; + // replace `|| vec![]` with `Vec::new` + span_lint_and_sugg( + cx, + REDUNDANT_CLOSURE, + expr.span, + "redundant closure", + "replace the closure with `Vec::new`", + format!("{vec_crate}::vec::Vec::new"), + Applicability::MachineApplicable, + ); } // skip `foo(|| macro!())` return; diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index 688979311c89..cdbfe7af8f9d 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -183,7 +183,7 @@ impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> { .collect() }; self.emit_sugg(spans, msg, help); - }; + } } } diff --git a/clippy_lints/src/functions/impl_trait_in_params.rs b/clippy_lints/src/functions/impl_trait_in_params.rs index 05e341e06fde..752dbc0db4db 100644 --- a/clippy_lints/src/functions/impl_trait_in_params.rs +++ b/clippy_lints/src/functions/impl_trait_in_params.rs @@ -45,7 +45,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body: for param in generics.params { if param.is_impl_trait() { report(cx, param, generics); - }; + } } } } diff --git a/clippy_lints/src/functions/must_use.rs b/clippy_lints/src/functions/must_use.rs index 1a01f5f885c3..90d3db2700fa 100644 --- a/clippy_lints/src/functions/must_use.rs +++ b/clippy_lints/src/functions/must_use.rs @@ -160,7 +160,7 @@ fn check_needless_must_use( && !is_must_use_ty(cx, future_ty) { return; - }; + } } span_lint_and_help( diff --git a/clippy_lints/src/implicit_saturating_add.rs b/clippy_lints/src/implicit_saturating_add.rs index dd5908553e59..41d2b18803d9 100644 --- a/clippy_lints/src/implicit_saturating_add.rs +++ b/clippy_lints/src/implicit_saturating_add.rs @@ -120,7 +120,7 @@ fn get_const<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<(u128, B let ecx = ConstEvalCtxt::new(cx); if let Some(Constant::Int(c)) = ecx.eval(r) { return Some((c, op.node, l)); - }; + } if let Some(Constant::Int(c)) = ecx.eval(l) { return Some((c, invert_op(op.node)?, r)); } diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index 37481dc7feb7..152d506a7c00 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -350,7 +350,7 @@ fn check_with_condition<'tcx>( if cx.typeck_results().expr_ty(cond_left).is_signed() { } else { print_lint_and_sugg(cx, var_name, expr); - }; + } } }, ExprKind::Path(QPath::TypeRelative(_, name)) => { diff --git a/clippy_lints/src/iter_over_hash_type.rs b/clippy_lints/src/iter_over_hash_type.rs index 5131f5b7269b..b1cb6da9475b 100644 --- a/clippy_lints/src/iter_over_hash_type.rs +++ b/clippy_lints/src/iter_over_hash_type.rs @@ -65,6 +65,6 @@ impl LateLintPass<'_> for IterOverHashType { expr.span, "iteration over unordered hash-based type", ); - }; + } } } diff --git a/clippy_lints/src/let_with_type_underscore.rs b/clippy_lints/src/let_with_type_underscore.rs index 2d2438514cc9..34ded6c65009 100644 --- a/clippy_lints/src/let_with_type_underscore.rs +++ b/clippy_lints/src/let_with_type_underscore.rs @@ -41,6 +41,6 @@ impl<'tcx> LateLintPass<'tcx> for UnderscoreTyped { Some(ty.span.with_lo(local.pat.span.hi())), "remove the explicit type `_` declaration", ); - }; + } } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index fad6f9d08803..4b700673d0f8 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -279,6 +279,7 @@ mod non_copy_const; mod non_expressive_names; mod non_octal_unix_permissions; mod non_send_fields_in_send_ty; +mod non_std_lazy_statics; mod non_zero_suggestions; mod nonstandard_macro_braces; mod octal_escapes; @@ -372,8 +373,10 @@ mod unnecessary_literal_bound; mod unnecessary_map_on_constructor; mod unnecessary_owned_empty_strings; mod unnecessary_self_imports; +mod unnecessary_semicolon; mod unnecessary_struct_initialization; mod unnecessary_wraps; +mod unneeded_struct_pattern; mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_async; @@ -680,7 +683,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unit_types::UnitTypes)); store.register_late_pass(move |_| Box::new(loops::Loops::new(conf))); store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(lifetimes::Lifetimes)); + store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf))); store.register_late_pass(|_| Box::new(entry::HashMapPass)); store.register_late_pass(|_| Box::new(minmax::MinMaxPass)); store.register_late_pass(|_| Box::new(zero_div_zero::ZeroDiv)); @@ -970,5 +973,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); + store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); + store.register_late_pass(|_| Box::::default()); + store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index e6761ea5c67c..c9ab0beb5dfa 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -1,4 +1,6 @@ +use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::trait_ref_of_method; use itertools::Itertools; use rustc_ast::visit::{try_visit, walk_list}; @@ -20,7 +22,7 @@ use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter as middle_nested_filter; use rustc_middle::lint::in_external_macro; -use rustc_session::declare_lint_pass; +use rustc_session::impl_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; use rustc_span::symbol::{Ident, kw}; @@ -91,7 +93,19 @@ declare_clippy_lint! { "unused lifetimes in function definitions" } -declare_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); +pub struct Lifetimes { + msrv: Msrv, +} + +impl Lifetimes { + pub fn new(conf: &'static Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + } + } +} + +impl_lint_pass!(Lifetimes => [NEEDLESS_LIFETIMES, EXTRA_UNUSED_LIFETIMES]); impl<'tcx> LateLintPass<'tcx> for Lifetimes { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { @@ -102,7 +116,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { .. } = item.kind { - check_fn_inner(cx, sig, Some(id), None, generics, item.span, true); + check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, &self.msrv); } else if let ItemKind::Impl(impl_) = item.kind { if !item.span.from_expansion() { report_extra_impl_lifetimes(cx, impl_); @@ -121,6 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { item.generics, item.span, report_extra_lifetimes, + &self.msrv, ); } } @@ -131,11 +146,14 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { TraitFn::Required(sig) => (None, Some(sig)), TraitFn::Provided(id) => (Some(id), None), }; - check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true); + check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, &self.msrv); } } + + extract_msrv_attr!(LateContext); } +#[allow(clippy::too_many_arguments)] fn check_fn_inner<'tcx>( cx: &LateContext<'tcx>, sig: &'tcx FnSig<'_>, @@ -144,6 +162,7 @@ fn check_fn_inner<'tcx>( generics: &'tcx Generics<'_>, span: Span, report_extra_lifetimes: bool, + msrv: &Msrv, ) { if in_external_macro(cx.sess(), span) || has_where_lifetimes(cx, generics) { return; @@ -195,7 +214,7 @@ fn check_fn_inner<'tcx>( } } - if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params) { + if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv) { if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) { return; } @@ -216,6 +235,7 @@ fn could_use_elision<'tcx>( body: Option, trait_sig: Option<&[Ident]>, named_generics: &'tcx [GenericParam<'_>], + msrv: &Msrv, ) -> Option<(Vec, Vec)> { // There are two scenarios where elision works: // * no output references, all input references have different LT @@ -249,17 +269,17 @@ fn could_use_elision<'tcx>( let input_lts = input_visitor.lts; let output_lts = output_visitor.lts; - if let Some(trait_sig) = trait_sig { - if explicit_self_type(cx, func, trait_sig.first().copied()) { - return None; - } + if let Some(trait_sig) = trait_sig + && non_elidable_self_type(cx, func, trait_sig.first().copied(), msrv) + { + return None; } if let Some(body_id) = body { let body = cx.tcx.hir().body(body_id); let first_ident = body.params.first().and_then(|param| param.pat.simple_ident()); - if explicit_self_type(cx, func, first_ident) { + if non_elidable_self_type(cx, func, first_ident, msrv) { return None; } @@ -332,9 +352,15 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxIndexSet(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option) -> bool { - if let Some(ident) = ident +// elision doesn't work for explicit self types before Rust 1.81, see rust-lang/rust#69064 +fn non_elidable_self_type<'tcx>( + cx: &LateContext<'tcx>, + func: &FnDecl<'tcx>, + ident: Option, + msrv: &Msrv, +) -> bool { + if !msrv.meets(msrvs::EXPLICIT_SELF_TYPE_ELISION) + && let Some(ident) = ident && ident.name == kw::SelfLower && !func.implicit_self.has_implicit_self() && let Some(self_ty) = func.inputs.first() @@ -488,11 +514,13 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ false } +#[allow(clippy::struct_excessive_bools)] struct Usage { lifetime: Lifetime, in_where_predicate: bool, in_bounded_ty: bool, in_generics_arg: bool, + lifetime_elision_impossible: bool, } struct LifetimeChecker<'cx, 'tcx, F> { @@ -501,6 +529,7 @@ struct LifetimeChecker<'cx, 'tcx, F> { where_predicate_depth: usize, bounded_ty_depth: usize, generic_args_depth: usize, + lifetime_elision_impossible: bool, phantom: std::marker::PhantomData, } @@ -525,6 +554,7 @@ where where_predicate_depth: 0, bounded_ty_depth: 0, generic_args_depth: 0, + lifetime_elision_impossible: false, phantom: std::marker::PhantomData, } } @@ -566,6 +596,7 @@ where in_where_predicate: self.where_predicate_depth != 0, in_bounded_ty: self.bounded_ty_depth != 0, in_generics_arg: self.generic_args_depth != 0, + lifetime_elision_impossible: self.lifetime_elision_impossible, }); } } @@ -592,11 +623,44 @@ where self.generic_args_depth -= 1; } + fn visit_fn_decl(&mut self, fd: &'tcx FnDecl<'tcx>) -> Self::Result { + self.lifetime_elision_impossible = !is_candidate_for_elision(fd); + walk_fn_decl(self, fd); + self.lifetime_elision_impossible = false; + } + fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } } +/// Check if `fd` supports function elision with an anonymous (or elided) lifetime, +/// and has a lifetime somewhere in its output type. +fn is_candidate_for_elision(fd: &FnDecl<'_>) -> bool { + struct V; + + impl Visitor<'_> for V { + type Result = ControlFlow; + + fn visit_lifetime(&mut self, lifetime: &Lifetime) -> Self::Result { + ControlFlow::Break(lifetime.is_elided() || lifetime.is_anonymous()) + } + } + + if fd.lifetime_elision_allowed + && let Return(ret_ty) = fd.output + && walk_unambig_ty(&mut V, ret_ty).is_break() + { + // The first encountered input lifetime will either be one on `self`, or will be the only lifetime. + fd.inputs + .iter() + .find_map(|ty| walk_unambig_ty(&mut V, ty).break_value()) + .unwrap() + } else { + false + } +} + fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) { let mut checker = LifetimeChecker::::new(cx, generics); @@ -662,6 +726,7 @@ fn report_elidable_impl_lifetimes<'tcx>( Usage { lifetime, in_where_predicate: false, + lifetime_elision_impossible: false, .. }, ] = usages.as_slice() @@ -719,7 +784,7 @@ fn report_elidable_lifetimes( |diag| { if !include_suggestions { return; - }; + } if let Some(suggestions) = elision_suggestions(cx, generics, elidable_lts, usages) { diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable); diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index e2dcb20f906d..a4cedf3bed35 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -251,7 +251,7 @@ impl LiteralDigitGrouping { ); if !consistent { return Err(WarningType::InconsistentDigitGrouping); - }; + } } Ok(()) diff --git a/clippy_lints/src/literal_string_with_formatting_args.rs b/clippy_lints/src/literal_string_with_formatting_args.rs index 49353a1b76be..a957c0e22a29 100644 --- a/clippy_lints/src/literal_string_with_formatting_args.rs +++ b/clippy_lints/src/literal_string_with_formatting_args.rs @@ -29,9 +29,9 @@ declare_clippy_lint! { /// let y = "hello"; /// x.expect(&format!("{y:?}")); /// ``` - #[clippy::version = "1.83.0"] + #[clippy::version = "1.85.0"] pub LITERAL_STRING_WITH_FORMATTING_ARGS, - suspicious, + nursery, "Checks if string literals have formatting arguments" } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index f3ca4a4a5715..c5e75af2303c 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -830,7 +830,7 @@ impl Loops { for_kv_map::check(cx, pat, arg, body); mut_range_bound::check(cx, arg, body); single_element_loop::check(cx, pat, arg, body, expr); - same_item_push::check(cx, pat, arg, body, expr); + same_item_push::check(cx, pat, arg, body, expr, &self.msrv); manual_flatten::check(cx, pat, arg, body, span); manual_find::check(cx, pat, arg, body, span, expr); unused_enumerate_index::check(cx, pat, arg, body); diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 951ebc9caef0..c27e930c99a5 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -1,8 +1,9 @@ use super::SAME_ITEM_PUSH; -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::path_to_local; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::msrvs::Msrv; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::{msrvs, path_to_local, std_or_core}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -19,19 +20,30 @@ pub(super) fn check<'tcx>( _: &'tcx Expr<'_>, body: &'tcx Expr<'_>, _: &'tcx Expr<'_>, + msrv: &Msrv, ) { - fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext) { + fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext, msrv: &Msrv) { let mut app = Applicability::Unspecified; let vec_str = snippet_with_context(cx, vec.span, ctxt, "", &mut app).0; let item_str = snippet_with_context(cx, pushed_item.span, ctxt, "", &mut app).0; - span_lint_and_help( + let secondary_help = if msrv.meets(msrvs::REPEAT_N) + && let Some(std_or_core) = std_or_core(cx) + { + format!("or `{vec_str}.extend({std_or_core}::iter::repeat_n({item_str}, SIZE))`") + } else { + format!("or `{vec_str}.resize(NEW_SIZE, {item_str})`") + }; + + span_lint_and_then( cx, SAME_ITEM_PUSH, vec.span, - "it looks like the same item is being pushed into this Vec", - None, - format!("consider using vec![{item_str};SIZE] or {vec_str}.resize(NEW_SIZE, {item_str})"), + "it looks like the same item is being pushed into this `Vec`", + |diag| { + diag.help(format!("consider using `vec![{item_str};SIZE]`")) + .help(secondary_help); + }, ); } @@ -67,11 +79,11 @@ pub(super) fn check<'tcx>( { match init.kind { // immutable bindings that are initialized with literal - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt), + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt, msrv), // immutable bindings that are initialized with constant ExprKind::Path(ref path) => { if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) { - emit_lint(cx, vec, pushed_item, ctxt); + emit_lint(cx, vec, pushed_item, ctxt, msrv); } }, _ => {}, @@ -79,11 +91,11 @@ pub(super) fn check<'tcx>( } }, // constant - Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt), + Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt, msrv), _ => {}, } }, - ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt), + ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt, msrv), _ => {}, } } diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index d2a2321dae80..17e25635ce10 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -6,7 +6,7 @@ use clippy_utils::source::snippet_with_context; use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; -use rustc_hir::{BinOpKind, Expr, ExprKind, QPath}; +use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; @@ -99,7 +99,7 @@ fn get_size_of_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option< && let QPath::Resolved(_, count_func_path) = count_func_qpath && let Some(segment_zero) = count_func_path.segments.first() && let Some(args) = segment_zero.args - && let Some(real_ty_span) = args.args.first().map(|arg| arg.span()) + && let Some(real_ty_span) = args.args.first().map(GenericArg::span) && let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id() && cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id) { diff --git a/clippy_lints/src/manual_div_ceil.rs b/clippy_lints/src/manual_div_ceil.rs index aa59b047b169..816ca17b3d2d 100644 --- a/clippy_lints/src/manual_div_ceil.rs +++ b/clippy_lints/src/manual_div_ceil.rs @@ -102,12 +102,48 @@ impl<'tcx> LateLintPass<'tcx> for ManualDivCeil { { build_suggestion(cx, expr, add_lhs, div_rhs, &mut applicability); } + + // (x + (Y - 1)) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_rhs, div_rhs) { + build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); + } + + // ((Y - 1) + x) / Y + if inner_op.node == BinOpKind::Add && differ_by_one(inner_lhs, div_rhs) { + build_suggestion(cx, expr, inner_rhs, div_rhs, &mut applicability); + } + + // (x - (-Y - 1)) / Y + if inner_op.node == BinOpKind::Sub + && let ExprKind::Unary(UnOp::Neg, abs_div_rhs) = div_rhs.kind + && differ_by_one(abs_div_rhs, inner_rhs) + { + build_suggestion(cx, expr, inner_lhs, div_rhs, &mut applicability); + } } } extract_msrv_attr!(LateContext); } +/// Checks if two expressions represent non-zero integer literals such that `small_expr + 1 == +/// large_expr`. +fn differ_by_one(small_expr: &Expr<'_>, large_expr: &Expr<'_>) -> bool { + if let ExprKind::Lit(small) = small_expr.kind + && let ExprKind::Lit(large) = large_expr.kind + && let LitKind::Int(s, _) = small.node + && let LitKind::Int(l, _) = large.node + { + Some(l.get()) == s.get().checked_add(1) + } else if let ExprKind::Unary(UnOp::Neg, small_inner_expr) = small_expr.kind + && let ExprKind::Unary(UnOp::Neg, large_inner_expr) = large_expr.kind + { + differ_by_one(large_inner_expr, small_inner_expr) + } else { + false + } +} + fn check_int_ty_and_feature(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { let expr_ty = cx.typeck_results().expr_ty(expr); match expr_ty.peel_refs().kind() { diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 9860deba8432..38106277a88f 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -7,7 +7,7 @@ use clippy_utils::{higher, is_in_const_context, path_to_local, peel_ref_operator use rustc_ast::LitKind::{Byte, Char}; use rustc_ast::ast::RangeLimits; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, Node, Param, PatKind, RangeEnd, PatExpr, PatExprKind, Lit}; +use rustc_hir::{Expr, ExprKind, Lit, Node, Param, PatExpr, PatExprKind, PatKind, RangeEnd}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; @@ -202,8 +202,14 @@ fn check_expr_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange { } fn check_range(start: &PatExpr<'_>, end: &PatExpr<'_>) -> CharRange { - if let PatExprKind::Lit{ lit: start_lit, negated: false } = &start.kind - && let PatExprKind::Lit{ lit: end_lit, negated: false } = &end.kind + if let PatExprKind::Lit { + lit: start_lit, + negated: false, + } = &start.kind + && let PatExprKind::Lit { + lit: end_lit, + negated: false, + } = &end.kind { check_lit_range(start_lit, end_lit) } else { diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index a70955a7c78d..8503dde3fb6b 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -106,7 +106,7 @@ impl<'tcx> QuestionMark { emit_manual_let_else(cx, stmt.span, match_expr, &ident_map, pat_arm.pat, diverging_arm.body); }, } - }; + } } } @@ -295,7 +295,7 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..) ) { return; - }; + } let ty = typeck_results.pat_ty(pat); // Option and Result are allowed, everything else isn't. if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) { diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index ebfd946b07ee..8aeec89f0bfa 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { } }, _ => return, - }; + } let mut app = Applicability::MachineApplicable; let rem_of = snippet_with_context(cx, rem2_lhs.span, ctxt, "_", &mut app).0; diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 79de41db3438..d69384a2cb70 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -86,7 +86,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip { let target_res = cx.qpath_res(target_path, target_arg.hir_id); if target_res == Res::Err { return; - }; + } if let Res::Local(hir_id) = target_res && let Some(used_mutably) = mutated_variables(then, cx) diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index cfa054706d6b..4cc43e427ec6 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -34,7 +34,7 @@ fn get_cond_expr<'tcx>( needs_negated: is_none_expr(cx, then_expr), /* if the `then_expr` resolves to `None`, need to negate the * cond */ }); - }; + } None } @@ -45,7 +45,7 @@ fn peels_blocks_incl_unsafe_opt<'a>(expr: &'a Expr<'a>) -> Option<&'a Expr<'a>> if block.stmts.is_empty() { return block.expr; } - }; + } None } @@ -68,14 +68,14 @@ fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: && is_res_lang_ctor(cx, path_res(cx, callee), OptionSome) && path_to_local_id(arg, target); } - }; + } false } fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) { return is_res_lang_ctor(cx, path_res(cx, inner_expr), OptionNone); - }; + } false } diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs new file mode 100644 index 000000000000..b1a555b91d1b --- /dev/null +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -0,0 +1,135 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::option_arg_ty; +use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks, span_contains_comment}; +use rustc_ast::BindingMode; +use rustc_errors::Applicability; +use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind, Path, QPath}; +use rustc_lint::{LateContext, LintContext}; +use rustc_middle::ty::Ty; +use rustc_span::symbol::Ident; + +use super::MANUAL_OK_ERR; + +pub(crate) fn check_if_let( + cx: &LateContext<'_>, + expr: &Expr<'_>, + let_pat: &Pat<'_>, + let_expr: &Expr<'_>, + if_then: &Expr<'_>, + else_expr: &Expr<'_>, +) { + if let Some(inner_expr_ty) = option_arg_ty(cx, cx.typeck_results().expr_ty(expr)) + && let Some((is_ok, ident)) = is_ok_or_err(cx, let_pat) + && is_some_ident(cx, if_then, ident, inner_expr_ty) + && is_none(cx, else_expr) + { + apply_lint(cx, expr, let_expr, is_ok); + } +} + +pub(crate) fn check_match(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) { + if let Some(inner_expr_ty) = option_arg_ty(cx, cx.typeck_results().expr_ty(expr)) + && arms.len() == 2 + && arms.iter().all(|arm| arm.guard.is_none()) + && let Some((idx, is_ok)) = arms.iter().enumerate().find_map(|(arm_idx, arm)| { + // Check if the arm is a `Ok(x) => x` or `Err(x) => x` alternative. + // In this case, return its index and whether it uses `Ok` or `Err`. + if let Some((is_ok, ident)) = is_ok_or_err(cx, arm.pat) + && is_some_ident(cx, arm.body, ident, inner_expr_ty) + { + Some((arm_idx, is_ok)) + } else { + None + } + }) + // Accept wildcard only as the second arm + && is_variant_or_wildcard(cx, arms[1-idx].pat, idx == 0, is_ok) + // Check that the body of the non `Ok`/`Err` arm is `None` + && is_none(cx, arms[1 - idx].body) + { + apply_lint(cx, expr, scrutinee, is_ok); + } +} + +/// Check that `pat` applied to a `Result` only matches `Ok(_)`, `Err(_)`, not a subset or a +/// superset of it. If `can_be_wild` is `true`, wildcards are also accepted. In the case of +/// a non-wildcard, `must_match_err` indicates whether the `Err` or the `Ok` variant should be +/// accepted. +fn is_variant_or_wildcard(cx: &LateContext<'_>, pat: &Pat<'_>, can_be_wild: bool, must_match_err: bool) -> bool { + match pat.kind { + PatKind::Wild | PatKind::Path(..) | PatKind::Binding(_, _, _, None) if can_be_wild => true, + PatKind::TupleStruct(qpath, ..) => { + is_res_lang_ctor(cx, cx.qpath_res(&qpath, pat.hir_id), ResultErr) == must_match_err + }, + PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _) => { + is_variant_or_wildcard(cx, pat, can_be_wild, must_match_err) + }, + _ => false, + } +} + +/// Return `Some((true, IDENT))` if `pat` contains `Ok(IDENT)`, `Some((false, IDENT))` if it +/// contains `Err(IDENT)`, `None` otherwise. +fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &'hir Ident)> { + if let PatKind::TupleStruct(qpath, [arg], _) = &pat.kind + && let PatKind::Binding(BindingMode::NONE, _, ident, _) = &arg.kind + && let res = cx.qpath_res(qpath, pat.hir_id) + && let Res::Def(DefKind::Ctor(..), id) = res + && let id @ Some(_) = cx.tcx.opt_parent(id) + { + let lang_items = cx.tcx.lang_items(); + if id == lang_items.result_ok_variant() { + return Some((true, ident)); + } else if id == lang_items.result_err_variant() { + return Some((false, ident)); + } + } + None +} + +/// Check if `expr` contains `Some(ident)`, possibly as a block +fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, ty: Ty<'tcx>) -> bool { + if let ExprKind::Call(body_callee, [body_arg]) = peel_blocks(expr).kind + && is_res_lang_ctor(cx, path_res(cx, body_callee), OptionSome) + && cx.typeck_results().expr_ty(body_arg) == ty + && let ExprKind::Path(QPath::Resolved( + _, + Path { + segments: [segment], .. + }, + )) = body_arg.kind + { + segment.ident.name == ident.name + } else { + false + } +} + +/// Check if `expr` is `None`, possibly as a block +fn is_none(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone) +} + +/// Suggest replacing `expr` by `scrutinee.METHOD()`, where `METHOD` is either `ok` or +/// `err`, depending on `is_ok`. +fn apply_lint(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Expr<'_>, is_ok: bool) { + let method = if is_ok { "ok" } else { "err" }; + let mut app = if span_contains_comment(cx.sess().source_map(), expr.span) { + Applicability::MaybeIncorrect + } else { + Applicability::MachineApplicable + }; + let scrut = Sugg::hir_with_applicability(cx, scrutinee, "..", &mut app).maybe_par(); + span_lint_and_sugg( + cx, + MANUAL_OK_ERR, + expr.span, + format!("manual implementation of `{method}`"), + "replace with", + format!("{scrut}.{method}()"), + app, + ); +} diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index bac5cf88cfbf..0b57740064c1 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -109,7 +109,7 @@ where } }, None => return None, - }; + } let mut app = Applicability::MachineApplicable; diff --git a/clippy_lints/src/matches/match_bool.rs b/clippy_lints/src/matches/match_bool.rs index 7e43d222a662..b90cf6357c5c 100644 --- a/clippy_lints/src/matches/match_bool.rs +++ b/clippy_lints/src/matches/match_bool.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::is_unit_expr; -use clippy_utils::source::{expr_block, snippet}; +use clippy_utils::source::expr_block; use clippy_utils::sugg::Sugg; use rustc_ast::LitKind; use rustc_errors::Applicability; @@ -17,17 +17,28 @@ pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>] cx, MATCH_BOOL, expr.span, - "you seem to be trying to match on a boolean expression", + "`match` on a boolean expression", move |diag| { if arms.len() == 2 { - // no guards - let exprs = if let PatKind::Expr(arm_bool) = arms[0].pat.kind { + let mut app = Applicability::MachineApplicable; + let test_sugg = if let PatKind::Expr(arm_bool) = arms[0].pat.kind { + let test = Sugg::hir_with_applicability(cx, scrutinee, "_", &mut app); if let PatExprKind::Lit { lit, .. } = arm_bool.kind { - match lit.node { - LitKind::Bool(true) => Some((arms[0].body, arms[1].body)), - LitKind::Bool(false) => Some((arms[1].body, arms[0].body)), + match &lit.node { + LitKind::Bool(true) => Some(test), + LitKind::Bool(false) => Some(!test), _ => None, } + .map(|test| { + if let Some(guard) = &arms[0] + .guard + .map(|g| Sugg::hir_with_applicability(cx, g, "_", &mut app)) + { + test.and(guard) + } else { + test + } + }) } else { None } @@ -35,39 +46,31 @@ pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>] None }; - if let Some((true_expr, false_expr)) = exprs { - let mut app = Applicability::HasPlaceholders; + if let Some(test_sugg) = test_sugg { let ctxt = expr.span.ctxt(); + let (true_expr, false_expr) = (arms[0].body, arms[1].body); let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) { (false, false) => Some(format!( "if {} {} else {}", - snippet(cx, scrutinee.span, "b"), + test_sugg, expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app), expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) )), (false, true) => Some(format!( "if {} {}", - snippet(cx, scrutinee.span, "b"), + test_sugg, expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app) )), - (true, false) => { - let test = Sugg::hir(cx, scrutinee, ".."); - Some(format!( - "if {} {}", - !test, - expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) - )) - }, + (true, false) => Some(format!( + "if {} {}", + !test_sugg, + expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app) + )), (true, true) => None, }; if let Some(sugg) = sugg { - diag.span_suggestion( - expr.span, - "consider using an `if`/`else` expression", - sugg, - Applicability::HasPlaceholders, - ); + diag.span_suggestion(expr.span, "consider using an `if`/`else` expression", sugg, app); } } } diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 223d0dc76569..d697f427c705 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -117,7 +117,7 @@ where if let ty::Ref(..) = cx.typeck_results().expr_ty(ex_inner).kind() { ex_new = ex_inner; } - }; + } span_lint_and_sugg( cx, MATCH_LIKE_MATCHES_MACRO, diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 9f5b7c855a13..df1b83cbb516 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -5,7 +5,7 @@ use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Arm, Expr, ExprKind, PatExpr, PatExprKind, LangItem, PatKind}; +use rustc_hir::{Arm, Expr, ExprKind, LangItem, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 91e40e4275c0..595655600890 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -170,7 +170,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { ); }); }, - }; + } } enum CommonPrefixSearcher<'a> { diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index ac1eae07eff6..a7fdd483c16c 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -2,6 +2,7 @@ mod collapsible_match; mod infallible_destructuring_match; mod manual_filter; mod manual_map; +mod manual_ok_err; mod manual_unwrap_or; mod manual_utils; mod match_as_ref; @@ -972,6 +973,40 @@ declare_clippy_lint! { "checks for unnecessary guards in match expressions" } +declare_clippy_lint! { + /// ### What it does + /// Checks for manual implementation of `.ok()` or `.err()` + /// on `Result` values. + /// + /// ### Why is this bad? + /// Using `.ok()` or `.err()` rather than a `match` or + /// `if let` is less complex and more readable. + /// + /// ### Example + /// ```no_run + /// # fn func() -> Result { Ok(0) } + /// let a = match func() { + /// Ok(v) => Some(v), + /// Err(_) => None, + /// }; + /// let b = if let Err(v) = func() { + /// Some(v) + /// } else { + /// None + /// }; + /// ``` + /// Use instead: + /// ```no_run + /// # fn func() -> Result { Ok(0) } + /// let a = func().ok(); + /// let b = func().err(); + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_OK_ERR, + complexity, + "find manual implementations of `.ok()` or `.err()` on `Result`" +} + pub struct Matches { msrv: Msrv, infallible_destructuring_match_linted: bool, @@ -1013,6 +1048,7 @@ impl_lint_pass!(Matches => [ MANUAL_MAP, MANUAL_FILTER, REDUNDANT_GUARDS, + MANUAL_OK_ERR, ]); impl<'tcx> LateLintPass<'tcx> for Matches { @@ -1091,6 +1127,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { 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); + manual_ok_err::check_match(cx, expr, ex, arms); } if self.infallible_destructuring_match_linted { @@ -1134,6 +1171,14 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if_let.if_then, else_expr, ); + manual_ok_err::check_if_let( + cx, + expr, + if_let.let_pat, + if_let.let_expr, + if_let.if_then, + else_expr, + ); } } redundant_pattern_match::check_if_let( diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 2ce6a8a85a5e..35f2e780d2e2 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::source::{first_line_of_span, indent_of, snippet}; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{get_attr, is_lint_allowed}; use itertools::Itertools; @@ -152,7 +152,7 @@ fn set_suggestion<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: & diag.multipart_suggestion( suggestion_message, vec![ - (expr.span.shrink_to_lo(), replacement), + (first_line_of_span(cx, expr.span).shrink_to_lo(), replacement), (found.found_span, scrutinee_replacement), ], Applicability::MaybeIncorrect, @@ -441,8 +441,9 @@ impl<'tcx> Visitor<'tcx> for SigDropHelper<'_, 'tcx> { let parent_expr_before = self.parent_expr.replace(ex); match ex.kind { - // Skip blocks because values in blocks will be dropped as usual. - ExprKind::Block(..) => (), + // Skip blocks because values in blocks will be dropped as usual, and await + // desugaring because temporary insides the future will have been dropped. + ExprKind::Block(..) | ExprKind::Match(_, _, MatchSource::AwaitDesugar) => (), _ => walk_expr(self, ex), } diff --git a/clippy_lints/src/matches/try_err.rs b/clippy_lints/src/matches/try_err.rs index 6c02207af49d..ff7769af1df4 100644 --- a/clippy_lints/src/matches/try_err.rs +++ b/clippy_lints/src/matches/try_err.rs @@ -46,7 +46,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutine err_ty = ty; } else { return; - }; + } span_lint_and_then( cx, diff --git a/clippy_lints/src/matches/wild_in_or_pats.rs b/clippy_lints/src/matches/wild_in_or_pats.rs index 390ba889fd2e..b75d1ab9a7aa 100644 --- a/clippy_lints/src/matches/wild_in_or_pats.rs +++ b/clippy_lints/src/matches/wild_in_or_pats.rs @@ -13,7 +13,7 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arms: &[Arm<'_>]) { && has_non_exhaustive_attr(cx.tcx, *adt_def) { return; - }; + } for arm in arms { if let PatKind::Or(fields) = arm.pat.kind { // look for multiple fields in this arm that contains at least one Wild pattern diff --git a/clippy_lints/src/methods/bytecount.rs b/clippy_lints/src/methods/bytecount.rs index 4a2124c74a88..687272e550bb 100644 --- a/clippy_lints/src/methods/bytecount.rs +++ b/clippy_lints/src/methods/bytecount.rs @@ -62,5 +62,5 @@ pub(super) fn check<'tcx>( ), applicability, ); - }; + } } diff --git a/clippy_lints/src/methods/bytes_count_to_len.rs b/clippy_lints/src/methods/bytes_count_to_len.rs index 34159f2d150e..a9f6a41c2357 100644 --- a/clippy_lints/src/methods/bytes_count_to_len.rs +++ b/clippy_lints/src/methods/bytes_count_to_len.rs @@ -32,5 +32,5 @@ pub(super) fn check<'tcx>( ), applicability, ); - }; + } } diff --git a/clippy_lints/src/methods/bytes_nth.rs b/clippy_lints/src/methods/bytes_nth.rs index a82abc79f2a2..de22514c37c6 100644 --- a/clippy_lints/src/methods/bytes_nth.rs +++ b/clippy_lints/src/methods/bytes_nth.rs @@ -46,5 +46,5 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, recv: &'tcx E format!("{receiver}.as_bytes().get({n}).copied()"), applicability, ); - }; + } } diff --git a/clippy_lints/src/methods/cloned_instead_of_copied.rs b/clippy_lints/src/methods/cloned_instead_of_copied.rs index 2a0a9d3710dc..223a960b800e 100644 --- a/clippy_lints/src/methods/cloned_instead_of_copied.rs +++ b/clippy_lints/src/methods/cloned_instead_of_copied.rs @@ -32,7 +32,7 @@ pub fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, // &T where T: Copy ty::Ref(_, ty, _) if is_copy(cx, *ty) => {}, _ => return, - }; + } span_lint_and_sugg( cx, CLONED_INSTEAD_OF_COPIED, diff --git a/clippy_lints/src/methods/drain_collect.rs b/clippy_lints/src/methods/drain_collect.rs index 10360b4817bc..cbf713a3b17c 100644 --- a/clippy_lints/src/methods/drain_collect.rs +++ b/clippy_lints/src/methods/drain_collect.rs @@ -1,8 +1,8 @@ use crate::methods::DRAIN_COLLECT; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_range_full; use clippy_utils::source::snippet; use clippy_utils::ty::is_type_lang_item; +use clippy_utils::{is_range_full, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, Path, QPath}; use rustc_lint::LateContext; @@ -58,12 +58,13 @@ pub(super) fn check(cx: &LateContext<'_>, args: &[Expr<'_>], expr: &Expr<'_>, re .then_some("Vec") .or_else(|| check_string(cx, args, expr_ty, recv_ty_no_refs, recv_path).then_some("String")) .or_else(|| check_collections(cx, expr_ty, recv_ty_no_refs)) + && let Some(exec_context) = std_or_core(cx) { let recv = snippet(cx, recv.span, ""); let sugg = if let ty::Ref(..) = recv_ty.kind() { - format!("std::mem::take({recv})") + format!("{exec_context}::mem::take({recv})") } else { - format!("std::mem::take(&mut {recv})") + format!("{exec_context}::mem::take(&mut {recv})") }; span_lint_and_sugg( diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs index 44b55570eead..f2786efa44cb 100644 --- a/clippy_lints/src/methods/err_expect.rs +++ b/clippy_lints/src/methods/err_expect.rs @@ -37,7 +37,7 @@ pub(super) fn check( "expect_err".to_string(), Applicability::MachineApplicable, ); - }; + } } /// Given a `Result` type, return its data (`T`). diff --git a/clippy_lints/src/methods/expect_fun_call.rs b/clippy_lints/src/methods/expect_fun_call.rs index 6dc48c26ba93..daa6e0e7f940 100644 --- a/clippy_lints/src/methods/expect_fun_call.rs +++ b/clippy_lints/src/methods/expect_fun_call.rs @@ -58,7 +58,7 @@ pub(super) fn check<'tcx>( if ty.is_str() && can_be_static_str(cx, arg) { return false; } - }; + } true } diff --git a/clippy_lints/src/methods/iter_with_drain.rs b/clippy_lints/src/methods/iter_with_drain.rs index 163058713377..aa45969c8982 100644 --- a/clippy_lints/src/methods/iter_with_drain.rs +++ b/clippy_lints/src/methods/iter_with_drain.rs @@ -25,5 +25,5 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span "into_iter()".to_string(), Applicability::MaybeIncorrect, ); - }; + } } diff --git a/clippy_lints/src/methods/manual_repeat_n.rs b/clippy_lints/src/methods/manual_repeat_n.rs new file mode 100644 index 000000000000..6e09bf132aa1 --- /dev/null +++ b/clippy_lints/src/methods/manual_repeat_n.rs @@ -0,0 +1,43 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::{snippet, snippet_with_context}; +use clippy_utils::{expr_use_ctxt, fn_def_id, is_trait_method, std_or_core}; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::MANUAL_REPEAT_N; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, + repeat_expr: &Expr<'_>, + take_arg: &Expr<'_>, + msrv: &Msrv, +) { + if msrv.meets(msrvs::REPEAT_N) + && !expr.span.from_expansion() + && is_trait_method(cx, expr, sym::Iterator) + && let ExprKind::Call(_, [repeat_arg]) = repeat_expr.kind + && let Some(def_id) = fn_def_id(cx, repeat_expr) + && cx.tcx.is_diagnostic_item(sym::iter_repeat, def_id) + && !expr_use_ctxt(cx, expr).is_ty_unified + && let Some(std_or_core) = std_or_core(cx) + { + let mut app = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + MANUAL_REPEAT_N, + expr.span, + "this `repeat().take()` can be written more concisely", + "consider using `repeat_n()` instead", + format!( + "{std_or_core}::iter::repeat_n({}, {})", + snippet_with_context(cx, repeat_arg.span, expr.span.ctxt(), "..", &mut app).0, + snippet(cx, take_arg.span, "..") + ), + app, + ); + } +} diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs index 1ebb71e251ab..78656ace831d 100644 --- a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{eager_or_lazy, higher, usage}; +use clippy_utils::{eager_or_lazy, higher, std_or_core, usage}; use rustc_ast::LitKind; use rustc_ast::ast::RangeLimits; use rustc_data_structures::packed::Pu128; @@ -75,6 +75,7 @@ pub(super) fn check( } = body_hir && !usage::BindingUsageFinder::are_params_used(cx, body_hir) && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) + && let Some(exec_context) = std_or_core(cx) { let method_to_use_name; let new_span; @@ -105,7 +106,7 @@ pub(super) fn check( let mut parts = vec![ ( receiver.span.to(method_call_span), - format!("std::iter::{method_to_use_name}"), + format!("{exec_context}::iter::{method_to_use_name}"), ), new_span, ]; diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 3965c4d40878..42418318fda8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -58,6 +58,7 @@ mod manual_inspect; mod manual_is_variant_and; mod manual_next_back; mod manual_ok_or; +mod manual_repeat_n; mod manual_saturating_arithmetic; mod manual_str_repeat; mod manual_try_fold; @@ -101,6 +102,7 @@ mod single_char_add_str; mod single_char_insert_string; mod single_char_push_string; mod skip_while_next; +mod sliced_string_as_bytes; mod stable_sort_primitive; mod str_split; mod str_splitn; @@ -130,6 +132,7 @@ mod unnecessary_to_owned; mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; +mod useless_nonzero_new_unchecked; mod utils; mod vec_resize_to_zero; mod verbose_file_reads; @@ -2416,14 +2419,14 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for usage of `.then_some(..).unwrap_or(..)` + /// Checks for unnecessary method chains that can be simplified into `if .. else ..`. /// /// ### Why is this bad? /// This can be written more clearly with `if .. else ..` /// /// ### Limitations /// This lint currently only looks for usages of - /// `.then_some(..).unwrap_or(..)`, but will be expanded + /// `.then_some(..).unwrap_or(..)` and `.then(..).unwrap_or(..)`, but will be expanded /// to account for similar patterns. /// /// ### Example @@ -4311,6 +4314,84 @@ declare_clippy_lint! { "using `Iterator::last` on a `DoubleEndedIterator`" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `NonZero*::new_unchecked()` being used in a `const` context. + /// + /// ### Why is this bad? + /// + /// Using `NonZero*::new_unchecked()` is an `unsafe` function and requires an `unsafe` context. When used in a + /// context evaluated at compilation time, `NonZero*::new().unwrap()` will provide the same result with identical + /// runtime performances while not requiring `unsafe`. + /// + /// ### Example + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; + /// ``` + /// Use instead: + /// ```no_run + /// use std::num::NonZeroUsize; + /// const PLAYERS: NonZeroUsize = NonZeroUsize::new(3).unwrap(); + /// ``` + #[clippy::version = "1.86.0"] + pub USELESS_NONZERO_NEW_UNCHECKED, + complexity, + "using `NonZero::new_unchecked()` in a `const` context" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `repeat().take()` that can be replaced with `repeat_n()`. + /// + /// ### Why is this bad? + /// + /// Using `repeat_n()` is more concise and clearer. Also, `repeat_n()` is sometimes faster than `repeat().take()` when the type of the element is non-trivial to clone because the original value can be reused for the last `.next()` call rather than always cloning. + /// + /// ### Example + /// ```no_run + /// let _ = std::iter::repeat(10).take(3); + /// ``` + /// Use instead: + /// ```no_run + /// let _ = std::iter::repeat_n(10, 3); + /// ``` + #[clippy::version = "1.86.0"] + pub MANUAL_REPEAT_N, + style, + "detect `repeat().take()` that can be replaced with `repeat_n()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for string slices immediantly followed by `as_bytes`. + /// + /// ### Why is this bad? + /// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic. + /// + /// ### Known problems + /// In some cases, the UTF-8 validation and potential panic from string slicing may be required for + /// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character + /// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred. + /// + /// ### Example + /// ```rust + /// let s = "Lorem ipsum"; + /// s[1..5].as_bytes(); + /// ``` + /// Use instead: + /// ```rust + /// let s = "Lorem ipsum"; + /// &s.as_bytes()[1..5]; + /// ``` + #[clippy::version = "1.86.0"] + pub SLICED_STRING_AS_BYTES, + perf, + "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4477,6 +4558,9 @@ impl_lint_pass!(Methods => [ MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, UNNECESSARY_MAP_OR, DOUBLE_ENDED_ITERATOR_LAST, + USELESS_NONZERO_NEW_UNCHECKED, + MANUAL_REPEAT_N, + SLICED_STRING_AS_BYTES, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4505,6 +4589,7 @@ impl<'tcx> LateLintPass<'tcx> for Methods { from_iter_instead_of_collect::check(cx, expr, args, func); unnecessary_fallible_conversions::check_function(cx, expr, func); manual_c_str_literals::check(cx, expr, func, args, &self.msrv); + useless_nonzero_new_unchecked::check(cx, expr, func, args, &self.msrv); }, ExprKind::MethodCall(method_call, receiver, args, _) => { let method_span = method_call.ident.span; @@ -4743,6 +4828,7 @@ impl Methods { if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { redundant_as_str::check(cx, expr, recv, as_str_span, span); } + sliced_string_as_bytes::check(cx, expr, recv); }, ("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv), ("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv), @@ -4924,8 +5010,8 @@ impl Methods { }, ("is_empty", []) => { match method_call(recv) { - Some(("as_bytes", prev_recv, [], _, _)) => { - needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span); + Some((prev_method @ ("as_bytes" | "bytes"), prev_recv, [], _, _)) => { + needless_as_bytes::check(cx, prev_method, "is_empty", prev_recv, expr.span); }, Some(("as_str", recv, [], as_str_span, _)) => { redundant_as_str::check(cx, expr, recv, as_str_span, span); @@ -4962,8 +5048,8 @@ impl Methods { double_ended_iterator_last::check(cx, expr, recv, call_span); }, ("len", []) => { - if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) { - needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span); + if let Some((prev_method @ ("as_bytes" | "bytes"), prev_recv, [], _, _)) = method_call(recv) { + needless_as_bytes::check(cx, prev_method, "len", prev_recv, expr.span); } }, ("lock", []) => { @@ -5015,7 +5101,7 @@ impl Methods { option_map_or_none::check(cx, expr, recv, def, map); manual_ok_or::check(cx, expr, recv, def, map); option_map_or_err_ok::check(cx, expr, recv, def, map); - unnecessary_map_or::check(cx, expr, recv, def, map, &self.msrv); + unnecessary_map_or::check(cx, expr, recv, def, map, span, &self.msrv); }, ("map_or_else", [def, map]) => { result_map_or_else_none::check(cx, expr, recv, def, map); @@ -5146,6 +5232,7 @@ impl Methods { ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), ("take", [arg]) => { iter_out_of_bounds::check_take(cx, expr, recv, arg); + manual_repeat_n::check(cx, expr, recv, arg, &self.msrv); if let Some(("cloned", recv2, [], _span2, _)) = method_call(recv) { iter_overeager_cloned::check( cx, @@ -5220,8 +5307,8 @@ impl Methods { Some(("map", m_recv, [m_arg], span, _)) => { option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, &self.msrv); }, - Some(("then_some", t_recv, [t_arg], _, _)) => { - obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg); + Some((then_method @ ("then" | "then_some"), t_recv, [t_arg], _, _)) => { + obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg, then_method); }, _ => {}, } diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs index 75e9f3172303..7c9f7bae9906 100644 --- a/clippy_lints/src/methods/needless_as_bytes.rs +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -8,18 +8,16 @@ use rustc_span::Span; use super::NEEDLESS_AS_BYTES; -pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) { - if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() - && let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs() - && (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str()) - { +pub fn check(cx: &LateContext<'_>, prev_method: &str, method: &str, prev_recv: &Expr<'_>, span: Span) { + let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs(); + if is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str() { let mut app = Applicability::MachineApplicable; let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app); span_lint_and_sugg( cx, NEEDLESS_AS_BYTES, span, - "needless call to `as_bytes()`", + format!("needless call to `{prev_method}`"), format!("`{method}()` can be called directly on strings"), format!("{sugg}.{method}()"), app, diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints/src/methods/obfuscated_if_else.rs index 697eab32a33b..b71f79f84824 100644 --- a/clippy_lints/src/methods/obfuscated_if_else.rs +++ b/clippy_lints/src/methods/obfuscated_if_else.rs @@ -1,8 +1,11 @@ use super::OBFUSCATED_IF_ELSE; use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; use rustc_errors::Applicability; use rustc_hir as hir; +use rustc_hir::ExprKind; use rustc_lint::LateContext; pub(super) fn check<'tcx>( @@ -11,19 +14,30 @@ pub(super) fn check<'tcx>( then_recv: &'tcx hir::Expr<'_>, then_arg: &'tcx hir::Expr<'_>, unwrap_arg: &'tcx hir::Expr<'_>, + then_method_name: &str, ) { - // something.then_some(blah).unwrap_or(blah) - // ^^^^^^^^^-then_recv ^^^^-then_arg ^^^^- unwrap_arg - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- expr - let recv_ty = cx.typeck_results().expr_ty(then_recv); if recv_ty.is_bool() { - let mut applicability = Applicability::MachineApplicable; + let mut applicability = if switch_to_eager_eval(cx, then_arg) && switch_to_eager_eval(cx, unwrap_arg) { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + + let if_then = match then_method_name { + "then" if let ExprKind::Closure(closure) = then_arg.kind => { + let body = cx.tcx.hir().body(closure.body); + snippet_with_applicability(cx, body.value.span, "..", &mut applicability) + }, + "then_some" => snippet_with_applicability(cx, then_arg.span, "..", &mut applicability), + _ => String::new().into(), + }; + let sugg = format!( "if {} {{ {} }} else {{ {} }}", - snippet_with_applicability(cx, then_recv.span, "..", &mut applicability), - snippet_with_applicability(cx, then_arg.span, "..", &mut applicability), + Sugg::hir_with_applicability(cx, then_recv, "..", &mut applicability), + if_then, snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability) ); @@ -31,8 +45,7 @@ pub(super) fn check<'tcx>( cx, OBFUSCATED_IF_ELSE, expr.span, - "use of `.then_some(..).unwrap_or(..)` can be written \ - more clearly with `if .. else ..`", + "this method chain can be written more clearly with `if .. else ..`", "try", sugg, applicability, diff --git a/clippy_lints/src/methods/path_ends_with_ext.rs b/clippy_lints/src/methods/path_ends_with_ext.rs index febd7fd5cf2f..b3811a335e1a 100644 --- a/clippy_lints/src/methods/path_ends_with_ext.rs +++ b/clippy_lints/src/methods/path_ends_with_ext.rs @@ -37,7 +37,7 @@ pub(super) fn check( let _ = write!(sugg, r#".extension().is_some_and(|ext| ext == "{path}")"#); } else { let _ = write!(sugg, r#".extension().map_or(false, |ext| ext == "{path}")"#); - }; + } span_lint_and_sugg( cx, diff --git a/clippy_lints/src/methods/sliced_string_as_bytes.rs b/clippy_lints/src/methods/sliced_string_as_bytes.rs new file mode 100644 index 000000000000..6d4cfdb34f31 --- /dev/null +++ b/clippy_lints/src/methods/sliced_string_as_bytes.rs @@ -0,0 +1,29 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_lang_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal}; +use rustc_lint::LateContext; + +use super::SLICED_STRING_AS_BYTES; + +pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { + if let ExprKind::Index(indexed, index, _) = recv.kind + && is_range_literal(index) + && let ty = cx.typeck_results().expr_ty(indexed).peel_refs() + && (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String)) + { + let mut applicability = Applicability::MaybeIncorrect; + let stringish = snippet_with_applicability(cx, indexed.span, "_", &mut applicability); + let range = snippet_with_applicability(cx, index.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + SLICED_STRING_AS_BYTES, + expr.span, + "calling `as_bytes` after slicing a string", + "try", + format!("&{stringish}.as_bytes()[{range}]"), + applicability, + ); + } +} diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 671c189a98e6..c0e015685881 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -87,7 +87,7 @@ pub fn check_for_loop_iter( // skip lint return true; } - }; + } // the lint should not be executed if no violation happens let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind diff --git a/clippy_lints/src/methods/unnecessary_map_or.rs b/clippy_lints/src/methods/unnecessary_map_or.rs index b7dbebe60a42..6dea1506d0e3 100644 --- a/clippy_lints/src/methods/unnecessary_map_or.rs +++ b/clippy_lints/src/methods/unnecessary_map_or.rs @@ -1,9 +1,8 @@ use std::borrow::Cow; -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::source::snippet_opt; use clippy_utils::sugg::{Sugg, make_binop}; use clippy_utils::ty::{get_type_diagnostic_name, implements_trait}; use clippy_utils::visitors::is_local_used; @@ -12,7 +11,7 @@ use rustc_ast::LitKind::Bool; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind}; use rustc_lint::LateContext; -use rustc_span::sym; +use rustc_span::{Span, sym}; use super::UNNECESSARY_MAP_OR; @@ -42,13 +41,14 @@ pub(super) fn check<'a>( recv: &Expr<'_>, def: &Expr<'_>, map: &Expr<'_>, + method_span: Span, msrv: &Msrv, ) { let ExprKind::Lit(def_kind) = def.kind else { return; }; - let recv_ty = cx.typeck_results().expr_ty(recv); + let recv_ty = cx.typeck_results().expr_ty_adjusted(recv); let Bool(def_bool) = def_kind.node else { return; @@ -60,6 +60,8 @@ pub(super) fn check<'a>( Some(_) | None => return, }; + let ext_def_span = def.span.until(map.span); + let (sugg, method, applicability) = if let ExprKind::Closure(map_closure) = map.kind && let closure_body = cx.tcx.hir().body(map_closure.body) && let closure_body_value = closure_body.value.peel_blocks() @@ -114,26 +116,17 @@ pub(super) fn check<'a>( } .into_string(); - (sugg, "a standard comparison", app) - } else if !def_bool - && msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) - && let Some(recv_callsite) = snippet_opt(cx, recv.span.source_callsite()) - && let Some(span_callsite) = snippet_opt(cx, map.span.source_callsite()) - { + (vec![(expr.span, sugg)], "a standard comparison", app) + } else if !def_bool && msrv.meets(msrvs::OPTION_RESULT_IS_VARIANT_AND) { let suggested_name = variant.method_name(); ( - format!("{recv_callsite}.{suggested_name}({span_callsite})",), + vec![(method_span, suggested_name.into()), (ext_def_span, String::default())], suggested_name, Applicability::MachineApplicable, ) - } else if def_bool - && matches!(variant, Variant::Some) - && msrv.meets(msrvs::IS_NONE_OR) - && let Some(recv_callsite) = snippet_opt(cx, recv.span.source_callsite()) - && let Some(span_callsite) = snippet_opt(cx, map.span.source_callsite()) - { + } else if def_bool && matches!(variant, Variant::Some) && msrv.meets(msrvs::IS_NONE_OR) { ( - format!("{recv_callsite}.is_none_or({span_callsite})"), + vec![(method_span, "is_none_or".into()), (ext_def_span, String::default())], "is_none_or", Applicability::MachineApplicable, ) @@ -145,13 +138,13 @@ pub(super) fn check<'a>( return; } - span_lint_and_sugg( + span_lint_and_then( cx, UNNECESSARY_MAP_OR, expr.span, "this `map_or` can be simplified", - format!("use {method} instead"), - sugg, - applicability, + |diag| { + diag.multipart_suggestion_verbose(format!("use {method} instead"), sugg, applicability); + }, ); } diff --git a/clippy_lints/src/methods/unnecessary_sort_by.rs b/clippy_lints/src/methods/unnecessary_sort_by.rs index 9a45b04d1a62..f0b29213e1e5 100644 --- a/clippy_lints/src/methods/unnecessary_sort_by.rs +++ b/clippy_lints/src/methods/unnecessary_sort_by.rs @@ -43,8 +43,7 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident && iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident)) }, // The two exprs are method calls. - // Check to see that the function is the same and the arguments are mirrored - // This is enough because the receiver of the method is listed in the arguments + // Check to see that the function is the same and the arguments and receivers are mirrored ( ExprKind::MethodCall(left_segment, left_receiver, left_args, _), ExprKind::MethodCall(right_segment, right_receiver, right_args, _), diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index 964f1603f0e5..7d72310c1c44 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -217,10 +217,13 @@ fn check_into_iter_call_arg( && implements_trait(cx, parent_ty, iterator_trait_id, &[]) && let Some(item_ty) = get_iterator_item_ty(cx, parent_ty) && let Some(receiver_snippet) = receiver.span.get_source_text(cx) + // If the receiver is a `Cow`, we can't remove the `into_owned` generally, see https://github.com/rust-lang/rust-clippy/issues/13624. + && !is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(receiver), sym::Cow) { if unnecessary_iter_cloned::check_for_loop_iter(cx, parent, method_name, receiver, true) { return true; } + let cloned_or_copied = if is_copy(cx, item_ty) && msrv.meets(msrvs::ITERATOR_COPIED) { "copied" } else { diff --git a/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs new file mode 100644 index 000000000000..0bd50429c09d --- /dev/null +++ b/clippy_lints/src/methods/useless_nonzero_new_unchecked.rs @@ -0,0 +1,59 @@ +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::is_inside_always_const_context; +use clippy_utils::msrvs::{self, Msrv}; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, Node, QPath, UnsafeSource}; +use rustc_lint::LateContext; +use rustc_span::sym; + +use super::USELESS_NONZERO_NEW_UNCHECKED; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'tcx>, args: &[Expr<'_>], msrv: &Msrv) { + if msrv.meets(msrvs::CONST_UNWRAP) + && let ExprKind::Path(QPath::TypeRelative(ty, segment)) = func.kind + && segment.ident.name == sym::new_unchecked + && let [init_arg] = args + && is_inside_always_const_context(cx.tcx, expr.hir_id) + && is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::NonZero) + { + let mut app = Applicability::MachineApplicable; + let ty_str = snippet_with_applicability(cx, ty.span, "_", &mut app); + let msg = format!("`{ty_str}::new()` and `Option::unwrap()` can be safely used in a `const` context"); + let sugg = format!( + "{ty_str}::new({}).unwrap()", + snippet_with_applicability(cx, init_arg.span, "_", &mut app) + ); + + if let Node::Block(Block { + stmts: [], + span: block_span, + rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), + .. + }) = cx.tcx.parent_hir_node(expr.hir_id) + { + if !block_span.from_expansion() { + // The expression is the only component of an `unsafe` block. Propose + // to replace the block altogether. + span_lint_and_sugg( + cx, + USELESS_NONZERO_NEW_UNCHECKED, + *block_span, + msg, + "use instead", + sugg, + app, + ); + } + } else { + // The expression is enclosed in a larger `unsafe` context. Indicate that + // this may no longer be needed for the fixed expression. + span_lint_and_then(cx, USELESS_NONZERO_NEW_UNCHECKED, expr.span, msg, |diagnostic| { + diagnostic + .span_suggestion(expr.span, "use instead", sugg, app) + .note("the fixed expression does not require an `unsafe` context"); + }); + } + } +} diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index b856c929cf67..b511b1e46b38 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -214,11 +214,12 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { ); }, ); - }; + } if let StmtKind::Semi(expr) = stmt.kind - && let ExprKind::Binary(ref binop, a, b) = expr.kind - && (binop.node == BinOpKind::And || binop.node == BinOpKind::Or) - && let Some(sugg) = Sugg::hir_opt(cx, a) + && let ExprKind::Binary(binop, a, b) = &expr.kind + && matches!(binop.node, BinOpKind::And | BinOpKind::Or) + && !stmt.span.from_expansion() + && expr.span.eq_ctxt(stmt.span) { span_lint_hir_and_then( cx, @@ -227,16 +228,14 @@ impl<'tcx> LateLintPass<'tcx> for LintPass { stmt.span, "boolean short circuit operator in statement may be clearer using an explicit test", |diag| { - let sugg = if binop.node == BinOpKind::Or { !sugg } else { sugg }; - diag.span_suggestion( - stmt.span, - "replace it with", - format!("if {sugg} {{ {}; }}", &snippet(cx, b.span, ".."),), - Applicability::MachineApplicable, // snippet - ); + let mut app = Applicability::MachineApplicable; + let test = Sugg::hir_with_context(cx, a, expr.span.ctxt(), "_", &mut app); + let test = if binop.node == BinOpKind::Or { !test } else { test }; + let then = Sugg::hir_with_context(cx, b, expr.span.ctxt(), "_", &mut app); + diag.span_suggestion(stmt.span, "replace it with", format!("if {test} {{ {then}; }}"), app); }, ); - }; + } } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index 748289454bea..d52fe7e7d5b9 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -66,7 +66,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch { }) => impl_params.push((path.segments[0].ident.to_string(), path.span)), GenericArg::Type(_) => return, _ => (), - }; + } } // find the type that the Impl is for diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 29dcbaa9e62a..06e92985e664 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -220,7 +220,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { | hir::ItemKind::GlobalAsm(..) | hir::ItemKind::Impl { .. } | hir::ItemKind::Use(..) => note_prev_span_then_ret!(self.prev_span, it.span), - }; + } let (article, desc) = cx.tcx.article_and_description(it.owner_id.to_def_id()); diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index f28b431ab997..e9ec23b1efa6 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -207,7 +207,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingFieldsInDebug { // this prevents ICEs such as when self is a type parameter or a primitive type // (see #10887, #11063) && let Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, self_path_did) = self_path.res - && cx.match_def_path(trait_def_id, &[sym::core, sym::fmt, sym::Debug]) + && cx.tcx.is_diagnostic_item(sym::Debug, trait_def_id) // don't trigger if this impl was derived && !cx.tcx.has_attr(item.owner_id, sym::automatically_derived) && !item.span.from_expansion() diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index 05aa425de9ed..bba1b63be277 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -135,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { | hir::ItemKind::ForeignMod { .. } | hir::ItemKind::Impl { .. } | hir::ItemKind::Use(..) => {}, - }; + } } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { diff --git a/clippy_lints/src/multi_assignments.rs b/clippy_lints/src/multi_assignments.rs index 9a6b1dfc52b5..4383f28717dc 100644 --- a/clippy_lints/src/multi_assignments.rs +++ b/clippy_lints/src/multi_assignments.rs @@ -56,10 +56,10 @@ impl EarlyLintPass for MultiAssignments { if let ExprKind::Assign(target, source, _) = &expr.kind { if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(target).kind { span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); - }; + } if let ExprKind::Assign(_target, _source, _) = &strip_paren_blocks(source).kind { span_lint(cx, MULTI_ASSIGNMENTS, expr.span, "assignments don't nest intuitively"); } - }; + } } } diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 79252bba74d7..aad6ae52a6db 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -171,7 +171,7 @@ fn collect_unsafe_exprs<'tcx>( }, _ => {}, - }; + } Continue::<(), _>(Descend::Yes) }); diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 388414964588..86c084423b71 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -91,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for Mutex { ty::Uint(t) if t != UintTy::Usize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), ty::Int(t) if t != IntTy::Isize => span_lint(cx, MUTEX_INTEGER, expr.span, msg), _ => span_lint(cx, MUTEX_ATOMIC, expr.span, msg), - }; + } } } } diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index a67addea9486..4e19a2f409dd 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -107,6 +107,10 @@ struct LocalAssign { impl LocalAssign { fn from_expr(expr: &Expr<'_>, span: Span) -> Option { + if expr.span.from_expansion() { + return None; + } + if let ExprKind::Assign(lhs, rhs, _) = expr.kind { if lhs.span.from_expansion() { return None; @@ -336,7 +340,7 @@ fn check<'tcx>( ); }, _ => {}, - }; + } Some(()) } diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 9ee4e4932777..2e2916c957da 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -37,7 +37,9 @@ declare_lint_pass!(NoMangleWithRustAbi => [NO_MANGLE_WITH_RUST_ABI]); impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { - if let ItemKind::Fn { sig: fn_sig, .. } = &item.kind { + if let ItemKind::Fn { sig: fn_sig, .. } = &item.kind + && !item.span.from_expansion() + { let attrs = cx.tcx.hir().attrs(item.hir_id()); let mut app = Applicability::MaybeIncorrect; let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(item.ident.span.lo()), "..", &mut app); diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 8409d179b0f5..147654675ec9 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -89,16 +89,6 @@ declare_clippy_lint! { /// /// The `const` value should be stored inside a `static` item. /// - /// ### Known problems - /// When an enum has variants with interior mutability, use of its non - /// interior mutable variants can generate false positives. See issue - /// [#3962](https://github.com/rust-lang/rust-clippy/issues/3962) - /// - /// Types that have underlying or potential interior mutability trigger the lint whether - /// the interior mutable field is used or not. See issues - /// [#5812](https://github.com/rust-lang/rust-clippy/issues/5812) and - /// [#3825](https://github.com/rust-lang/rust-clippy/issues/3825) - /// /// ### Example /// ```no_run /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 0caa19cd8443..852c3885f568 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -73,7 +73,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { } }, _ => {}, - }; + } } } diff --git a/clippy_lints/src/non_std_lazy_statics.rs b/clippy_lints/src/non_std_lazy_statics.rs new file mode 100644 index 000000000000..312610db0423 --- /dev/null +++ b/clippy_lints/src/non_std_lazy_statics.rs @@ -0,0 +1,306 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::msrvs::Msrv; +use clippy_utils::visitors::for_each_expr; +use clippy_utils::{def_path_def_ids, fn_def_id, path_def_id}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::{CrateNum, DefId}; +use rustc_hir::{self as hir, BodyId, Expr, ExprKind, Item, ItemKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::impl_lint_pass; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Lints when `once_cell::sync::Lazy` or `lazy_static!` are used to define a static variable, + /// and suggests replacing such cases with `std::sync::LazyLock` instead. + /// + /// Note: This lint will not trigger in crate with `no_std` context, or with MSRV < 1.80.0. It + /// also will not trigger on `once_cell::sync::Lazy` usage in crates which use other types + /// from `once_cell`, such as `once_cell::race::OnceBox`. + /// + /// ### Why restrict this? + /// - Reduces the need for an extra dependency + /// - Enforce convention of using standard library types when possible + /// + /// ### Example + /// ```ignore + /// lazy_static! { + /// static ref FOO: String = "foo".to_uppercase(); + /// } + /// static BAR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| "BAR".to_lowercase()); + /// ``` + /// Use instead: + /// ```ignore + /// static FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "FOO".to_lowercase()); + /// static BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| "BAR".to_lowercase()); + /// ``` + #[clippy::version = "1.81.0"] + pub NON_STD_LAZY_STATICS, + pedantic, + "lazy static that could be replaced by `std::sync::LazyLock`" +} + +/// A list containing functions with corresponding replacements in `LazyLock`. +/// +/// Some functions could be replaced as well if we have replaced `Lazy` to `LazyLock`, +/// therefore after suggesting replace the type, we need to make sure the function calls can be +/// replaced, otherwise the suggestions cannot be applied thus the applicability should be +/// `Unspecified` or `MaybeIncorret`. +static FUNCTION_REPLACEMENTS: &[(&str, Option<&str>)] = &[ + ("once_cell::sync::Lazy::force", Some("std::sync::LazyLock::force")), + ("once_cell::sync::Lazy::get", None), // `std::sync::LazyLock::get` is experimental + ("once_cell::sync::Lazy::new", Some("std::sync::LazyLock::new")), + // Note: `Lazy::{into_value, get_mut, force_mut}` are not in the list. + // Because the lint only checks for `static`s, and using these functions with statics + // will either be a hard error or triggers `static_mut_ref` that will be hard errors. + // But keep in mind that if somehow we decide to expand this lint to catch non-statics, + // add those functions into the list. +]; + +pub struct NonStdLazyStatic { + msrv: Msrv, + lazy_static_lazy_static: Vec, + once_cell_crate: Vec, + once_cell_sync_lazy: Vec, + once_cell_sync_lazy_new: Vec, + sugg_map: FxIndexMap>, + lazy_type_defs: FxIndexMap, + uses_other_once_cell_types: bool, +} + +impl NonStdLazyStatic { + #[must_use] + pub fn new(conf: &'static Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + lazy_static_lazy_static: Vec::new(), + once_cell_crate: Vec::new(), + once_cell_sync_lazy: Vec::new(), + once_cell_sync_lazy_new: Vec::new(), + sugg_map: FxIndexMap::default(), + lazy_type_defs: FxIndexMap::default(), + uses_other_once_cell_types: false, + } + } +} + +impl_lint_pass!(NonStdLazyStatic => [NON_STD_LAZY_STATICS]); + +/// Return if current MSRV does not meet the requirement for `lazy_cell` feature, +/// or current context has `no_std` attribute. +macro_rules! ensure_prerequisite { + ($msrv:expr, $cx:ident) => { + if !$msrv.meets(clippy_utils::msrvs::LAZY_CELL) || clippy_utils::is_no_std_crate($cx) { + return; + } + }; +} + +impl<'hir> LateLintPass<'hir> for NonStdLazyStatic { + extract_msrv_attr!(LateContext); + + fn check_crate(&mut self, cx: &LateContext<'hir>) { + // Do not lint if current crate does not support `LazyLock`. + ensure_prerequisite!(self.msrv, cx); + + // Fetch def_ids for external paths + self.lazy_static_lazy_static = def_path_def_ids(cx.tcx, &["lazy_static", "lazy_static"]).collect(); + self.once_cell_sync_lazy = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy"]).collect(); + self.once_cell_sync_lazy_new = def_path_def_ids(cx.tcx, &["once_cell", "sync", "Lazy", "new"]).collect(); + // And CrateNums for `once_cell` crate + self.once_cell_crate = self.once_cell_sync_lazy.iter().map(|d| d.krate).collect(); + + // Convert hardcoded fn replacement list into a map with def_id + for (path, sugg) in FUNCTION_REPLACEMENTS { + let path_vec: Vec<&str> = path.split("::").collect(); + for did in def_path_def_ids(cx.tcx, &path_vec) { + self.sugg_map.insert(did, sugg.map(ToOwned::to_owned)); + } + } + } + + fn check_item(&mut self, cx: &LateContext<'hir>, item: &Item<'hir>) { + ensure_prerequisite!(self.msrv, cx); + + if let ItemKind::Static(..) = item.kind + && let Some(macro_call) = clippy_utils::macros::root_macro_call(item.span) + && self.lazy_static_lazy_static.contains(¯o_call.def_id) + { + span_lint( + cx, + NON_STD_LAZY_STATICS, + macro_call.span, + "this macro has been superceded by `std::sync::LazyLock`", + ); + return; + } + + if in_external_macro(cx.sess(), item.span) { + return; + } + + if let Some(lazy_info) = LazyInfo::from_item(self, cx, item) { + self.lazy_type_defs.insert(item.owner_id.to_def_id(), lazy_info); + } + } + + fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &Expr<'hir>) { + ensure_prerequisite!(self.msrv, cx); + + // All functions in the `FUNCTION_REPLACEMENTS` have only one args + if let ExprKind::Call(callee, [arg]) = expr.kind + && let Some(call_def_id) = fn_def_id(cx, expr) + && self.sugg_map.contains_key(&call_def_id) + && let ExprKind::Path(qpath) = arg.peel_borrows().kind + && let Some(arg_def_id) = cx.typeck_results().qpath_res(&qpath, arg.hir_id).opt_def_id() + && let Some(lazy_info) = self.lazy_type_defs.get_mut(&arg_def_id) + { + lazy_info.calls_span_and_id.insert(callee.span, call_def_id); + } + } + + fn check_ty(&mut self, cx: &LateContext<'hir>, ty: &'hir rustc_hir::Ty<'hir, rustc_hir::AmbigArg>) { + ensure_prerequisite!(self.msrv, cx); + + // Record if types from `once_cell` besides `sync::Lazy` are used. + if let rustc_hir::TyKind::Path(qpath) = ty.peel_refs().kind + && let Some(ty_def_id) = cx.qpath_res(&qpath, ty.hir_id).opt_def_id() + // Is from `once_cell` crate + && self.once_cell_crate.contains(&ty_def_id.krate) + // And is NOT `once_cell::sync::Lazy` + && !self.once_cell_sync_lazy.contains(&ty_def_id) + { + self.uses_other_once_cell_types = true; + } + } + + fn check_crate_post(&mut self, cx: &LateContext<'hir>) { + ensure_prerequisite!(self.msrv, cx); + + if !self.uses_other_once_cell_types { + for (_, lazy_info) in &self.lazy_type_defs { + lazy_info.lint(cx, &self.sugg_map); + } + } + } +} + +struct LazyInfo { + /// Span of the [`hir::Ty`] without including args. + /// i.e.: + /// ```ignore + /// static FOO: Lazy = Lazy::new(...); + /// // ^^^^ + /// ``` + ty_span_no_args: Span, + /// `Span` and `DefId` of calls on `Lazy` type. + /// i.e.: + /// ```ignore + /// static FOO: Lazy = Lazy::new(...); + /// // ^^^^^^^^^ + /// ``` + calls_span_and_id: FxIndexMap, +} + +impl LazyInfo { + fn from_item(state: &NonStdLazyStatic, cx: &LateContext<'_>, item: &Item<'_>) -> Option { + // Check if item is a `once_cell:sync::Lazy` static. + if let ItemKind::Static(ty, _, body_id) = item.kind + && let Some(path_def_id) = path_def_id(cx, ty) + && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind + && state.once_cell_sync_lazy.contains(&path_def_id) + { + let ty_span_no_args = path_span_without_args(path); + let body = cx.tcx.hir().body(body_id); + + // visit body to collect `Lazy::new` calls + let mut new_fn_calls = FxIndexMap::default(); + for_each_expr::<(), ()>(cx, body, |ex| { + if let Some((fn_did, call_span)) = fn_def_id_and_span_from_body(cx, ex, body_id) + && state.once_cell_sync_lazy_new.contains(&fn_did) + { + new_fn_calls.insert(call_span, fn_did); + } + std::ops::ControlFlow::Continue(()) + }); + + Some(LazyInfo { + ty_span_no_args, + calls_span_and_id: new_fn_calls, + }) + } else { + None + } + } + + fn lint(&self, cx: &LateContext<'_>, sugg_map: &FxIndexMap>) { + // Applicability might get adjusted to `Unspecified` later if any calls + // in `calls_span_and_id` are not replaceable judging by the `sugg_map`. + let mut appl = Applicability::MachineApplicable; + let mut suggs = vec![(self.ty_span_no_args, "std::sync::LazyLock".to_string())]; + + for (span, def_id) in &self.calls_span_and_id { + let maybe_sugg = sugg_map.get(def_id).cloned().flatten(); + if let Some(sugg) = maybe_sugg { + suggs.push((*span, sugg)); + } else { + // If NO suggested replacement, not machine applicable + appl = Applicability::Unspecified; + } + } + + span_lint_and_then( + cx, + NON_STD_LAZY_STATICS, + self.ty_span_no_args, + "this type has been superceded by `LazyLock` in the standard library", + |diag| { + diag.multipart_suggestion("use `std::sync::LazyLock` instead", suggs, appl); + }, + ); + } +} + +/// Return the span of a given `Path` without including any of its args. +/// +/// NB: Re-write of a private function `rustc_lint::non_local_def::path_span_without_args`. +fn path_span_without_args(path: &hir::Path<'_>) -> Span { + path.segments + .last() + .and_then(|seg| seg.args) + .map_or(path.span, |args| path.span.until(args.span_ext)) +} + +/// Returns the `DefId` and `Span` of the callee if the given expression is a function call. +/// +/// NB: Modified from [`clippy_utils::fn_def_id`], to support calling in an static `Item`'s body. +fn fn_def_id_and_span_from_body(cx: &LateContext<'_>, expr: &Expr<'_>, body_id: BodyId) -> Option<(DefId, Span)> { + // FIXME: find a way to cache the result. + let typeck = cx.tcx.typeck_body(body_id); + match &expr.kind { + ExprKind::Call( + Expr { + kind: ExprKind::Path(qpath), + hir_id: path_hir_id, + span, + .. + }, + .., + ) => { + // Only return Fn-like DefIds, not the DefIds of statics/consts/etc that contain or + // deref to fn pointers, dyn Fn, impl Fn - #8850 + if let Res::Def(DefKind::Fn | DefKind::Ctor(..) | DefKind::AssocFn, id) = + typeck.qpath_res(qpath, *path_hir_id) + { + Some((id, *span)) + } else { + None + } + }, + _ => None, + } +} diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 0eca788c7874..9d07a14718da 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -104,7 +104,7 @@ impl ArithmeticSideEffects { if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { return false; - }; + } let int_type = substs.type_at(0); let unsigned_int_types = [ @@ -214,13 +214,13 @@ impl ArithmeticSideEffects { | hir::BinOpKind::Sub ) { return; - }; + } let (mut actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs); let (mut actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs); actual_lhs = expr_or_init(cx, actual_lhs); actual_rhs = expr_or_init(cx, actual_rhs); let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); - let rhs_ty = cx.typeck_results().expr_ty(actual_rhs).peel_refs(); + let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs(); if self.has_allowed_binary(lhs_ty, rhs_ty) { return; } @@ -283,7 +283,7 @@ impl ArithmeticSideEffects { if ConstEvalCtxt::new(cx).eval_simple(receiver).is_some() { return; } - let instance_ty = cx.typeck_results().expr_ty(receiver); + let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); if !Self::is_integral(instance_ty) { return; } @@ -311,7 +311,7 @@ impl ArithmeticSideEffects { if ConstEvalCtxt::new(cx).eval(un_expr).is_some() { return; } - let ty = cx.typeck_results().expr_ty(expr).peel_refs(); + let ty = cx.typeck_results().expr_ty_adjusted(expr).peel_refs(); if self.has_allowed_unary(ty) { return; } diff --git a/clippy_lints/src/operators/cmp_owned.rs b/clippy_lints/src/operators/cmp_owned.rs index b0d872e98fd4..cf6b8992973a 100644 --- a/clippy_lints/src/operators/cmp_owned.rs +++ b/clippy_lints/src/operators/cmp_owned.rs @@ -104,7 +104,7 @@ fn check_op(cx: &LateContext<'_>, expr: &Expr<'_>, other: &Expr<'_>, left: bool) } else { expr_snip = arg_snip.to_string(); eq_impl = without_deref; - }; + } let span; let hint; diff --git a/clippy_lints/src/operators/const_comparisons.rs b/clippy_lints/src/operators/const_comparisons.rs index 1a0bfd8b9970..10455d3b93a0 100644 --- a/clippy_lints/src/operators/const_comparisons.rs +++ b/clippy_lints/src/operators/const_comparisons.rs @@ -127,7 +127,7 @@ pub(super) fn check<'tcx>( None, note, ); - }; + } } } diff --git a/clippy_lints/src/operators/double_comparison.rs b/clippy_lints/src/operators/double_comparison.rs index d72a2fc3b1ab..54f50f11e034 100644 --- a/clippy_lints/src/operators/double_comparison.rs +++ b/clippy_lints/src/operators/double_comparison.rs @@ -49,5 +49,5 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, op: BinOpKind, lhs: &'tcx Expr lint_double_comparison!(==); }, _ => (), - }; + } } diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index 8272d3643d42..01dc6a27c33e 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -120,7 +120,7 @@ fn is_float(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let ty::Array(arr_ty, _) = value { return matches!(arr_ty.kind(), ty::Float(_)); - }; + } matches!(value, ty::Float(_)) } diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 9e8a821c3f4e..d9845bc3b0f7 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -262,7 +262,7 @@ declare_clippy_lint! { /// to `trailing_zeros` /// /// ### Why is this bad? - /// `x.trailing_zeros() > 4` is much clearer than `x & 15 + /// `x.trailing_zeros() >= 4` is much clearer than `x & 15 /// == 0` /// /// ### Known problems @@ -278,7 +278,7 @@ declare_clippy_lint! { /// /// ```no_run /// # let x: i32 = 1; - /// if x.trailing_zeros() > 4 { } + /// if x.trailing_zeros() >= 4 { } /// ``` #[clippy::version = "pre 1.29.0"] pub VERBOSE_BIT_MASK, diff --git a/clippy_lints/src/operators/modulo_arithmetic.rs b/clippy_lints/src/operators/modulo_arithmetic.rs index c83bdda347a6..691d7b904eff 100644 --- a/clippy_lints/src/operators/modulo_arithmetic.rs +++ b/clippy_lints/src/operators/modulo_arithmetic.rs @@ -30,7 +30,7 @@ pub(super) fn check<'tcx>( } else { check_non_const_operands(cx, e, lhs); } - }; + } } fn used_in_comparison_with_zero(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { diff --git a/clippy_lints/src/operators/modulo_one.rs b/clippy_lints/src/operators/modulo_one.rs index 54eea14833ff..fc5565e821ed 100644 --- a/clippy_lints/src/operators/modulo_one.rs +++ b/clippy_lints/src/operators/modulo_one.rs @@ -21,6 +21,6 @@ pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, op: BinOpKind, right: "any number modulo -1 will panic/overflow or result in 0", ); } - }; + } } } diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 794bef7b3214..55676522419c 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -53,6 +53,6 @@ impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { ); } } - }; + } } } diff --git a/clippy_lints/src/precedence.rs b/clippy_lints/src/precedence.rs index 031f09310590..421b2b747555 100644 --- a/clippy_lints/src/precedence.rs +++ b/clippy_lints/src/precedence.rs @@ -43,7 +43,7 @@ impl EarlyLintPass for Precedence { cx, PRECEDENCE, expr.span, - "operator precedence can trip the unwary", + "operator precedence might not be obvious", "consider parenthesizing your expression", sugg, appl, diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 41a44de536b1..b4dadef57a3c 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -208,7 +208,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall { // avoid clippy::double_parens if !is_in_fn_call_arg { hint = hint.maybe_par(); - }; + } diag.span_suggestion(full_expr.span, "try doing something like", hint, applicability); } diff --git a/clippy_lints/src/redundant_pub_crate.rs b/clippy_lints/src/redundant_pub_crate.rs index 1b557730ecad..8d6b1c7274d9 100644 --- a/clippy_lints/src/redundant_pub_crate.rs +++ b/clippy_lints/src/redundant_pub_crate.rs @@ -1,8 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::HasSession; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Item, ItemKind}; 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::CRATE_DEF_ID; @@ -49,6 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantPubCrate { && !cx.effective_visibilities.is_exported(item.owner_id.def_id) && self.is_exported.last() == Some(&false) && is_not_macro_export(item) + && !in_external_macro(cx.sess(), item.span) { let span = item.span.with_hi(item.ident.span.hi()); let descr = cx.tcx.def_kind(item.owner_id).descr(item.owner_id.to_def_id()); diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 81556f396141..7bd4d6e993b4 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -215,6 +215,6 @@ impl LateLintPass<'_> for RedundantTypeAnnotations { }, _ => (), } - }; + } } } diff --git a/clippy_lints/src/repeat_vec_with_capacity.rs b/clippy_lints/src/repeat_vec_with_capacity.rs index f54cafffb83c..5dddf9263a35 100644 --- a/clippy_lints/src/repeat_vec_with_capacity.rs +++ b/clippy_lints/src/repeat_vec_with_capacity.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::macros::matching_root_macro_call; use clippy_utils::source::snippet; -use clippy_utils::{expr_or_init, fn_def_id}; +use clippy_utils::{expr_or_init, fn_def_id, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -93,6 +93,7 @@ fn check_repeat_fn(cx: &LateContext<'_>, expr: &Expr<'_>) { && let ExprKind::Call(_, [repeat_expr]) = expr.kind && fn_def_id(cx, repeat_expr).is_some_and(|did| cx.tcx.is_diagnostic_item(sym::vec_with_capacity, did)) && !repeat_expr.span.from_expansion() + && let Some(exec_context) = std_or_core(cx) { emit_lint( cx, @@ -100,7 +101,10 @@ fn check_repeat_fn(cx: &LateContext<'_>, expr: &Expr<'_>) { "iter::repeat", "none of the yielded `Vec`s will have the requested capacity", "if you intended to create an iterator that yields `Vec`s with an initial capacity, try", - format!("std::iter::repeat_with(|| {})", snippet(cx, repeat_expr.span, "..")), + format!( + "{exec_context}::iter::repeat_with(|| {})", + snippet(cx, repeat_expr.span, "..") + ), ); } } diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index dfaee8cc3054..664e984fece3 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -1,10 +1,11 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; -use clippy_utils::visitors::{Descend, for_each_expr, for_each_unconsumed_temporary}; +use clippy_utils::visitors::{Descend, for_each_expr}; use clippy_utils::{ - binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, - path_to_local_id, span_contains_cfg, span_find_starting_semi, + binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, + leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg, + span_find_starting_semi, }; use core::ops::ControlFlow; use rustc_ast::MetaItemInner; @@ -389,22 +390,8 @@ fn check_final_expr<'tcx>( } }; - if let Some(inner) = inner { - if for_each_unconsumed_temporary(cx, inner, |temporary_ty| { - if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env()) - && temporary_ty - .walk() - .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if !re.is_static())) - { - ControlFlow::Break(()) - } else { - ControlFlow::Continue(()) - } - }) - .is_break() - { - return; - } + if inner.is_some_and(|inner| leaks_droppable_temporary_with_limited_lifetime(cx, inner)) { + return; } if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index 9737b84cdb9c..2d989b1cf0ba 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::get_trait_def_id; use clippy_utils::higher::VecArgs; use clippy_utils::macros::root_macro_call_first_node; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; +use clippy_utils::{get_trait_def_id, is_no_std_crate}; use rustc_ast::{LitIntType, LitKind, UintTy}; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, LangItem, QPath, StructTailExpr}; @@ -125,7 +125,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { span, format!("{suggested_type} of `Range` that is only one element"), |diag| { - if should_emit_every_value { + if should_emit_every_value && !is_no_std_crate(cx) { diag.span_suggestion( span, "if you wanted a `Vec` that contains the entire range, try", diff --git a/clippy_lints/src/size_of_in_element_count.rs b/clippy_lints/src/size_of_in_element_count.rs index db1c75fc3dea..f72ff10dd43c 100644 --- a/clippy_lints/src/size_of_in_element_count.rs +++ b/clippy_lints/src/size_of_in_element_count.rs @@ -97,7 +97,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( && let Some(pointee_ty) = cx.typeck_results().node_args(func.hir_id).types().next() { return Some((pointee_ty, count)); - }; + } if let ExprKind::MethodCall(method_path, ptr_self, [.., count], _) = expr.kind // Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods && let method_ident = method_path.ident.as_str() @@ -108,7 +108,7 @@ fn get_pointee_ty_and_count_expr<'tcx>( cx.typeck_results().expr_ty(ptr_self).kind() { return Some((*pointee_ty, count)); - }; + } None } @@ -130,6 +130,6 @@ impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount { && pointee_ty == ty_used_for_size_of { span_lint_and_help(cx, SIZE_OF_IN_ELEMENT_COUNT, count_expr.span, LINT_MSG, None, HELP_MSG); - }; + } } } diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index d2d693eaa1f3..d26288adb391 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -3,6 +3,7 @@ use clippy_utils::macros::matching_root_macro_call; use clippy_utils::sugg::Sugg; use clippy_utils::{ SpanlessEq, get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, + span_contains_comment, }; use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_stmt}; @@ -190,7 +191,7 @@ impl SlowVectorInit { InitializationType::Extend(e) | InitializationType::Resize(e) => { Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization"); }, - }; + } } fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &'static str) { @@ -206,6 +207,14 @@ impl SlowVectorInit { let span_to_replace = slow_fill .span .with_lo(vec_alloc.allocation_expr.span.source_callsite().lo()); + + // If there is no comment in `span_to_replace`, Clippy can automatically fix the code. + let app = if span_contains_comment(cx.tcx.sess.source_map(), span_to_replace) { + Applicability::Unspecified + } else { + Applicability::MachineApplicable + }; + span_lint_and_sugg( cx, SLOW_VECTOR_INITIALIZATION, @@ -213,7 +222,7 @@ impl SlowVectorInit { msg, "consider replacing this with", format!("vec![0; {len_expr}]"), - Applicability::Unspecified, + app, ); } } diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index ff1168005123..9b4c3d275ae7 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -296,7 +296,7 @@ fn check_xor_swap<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) { { let span = s1.span.to(s3.span); generate_swap_warning(block, cx, lhs0, rhs0, rhs1, rhs2, span, true); - }; + } } } diff --git a/clippy_lints/src/transmute/missing_transmute_annotations.rs b/clippy_lints/src/transmute/missing_transmute_annotations.rs index 531422798a68..bed4e60ba62d 100644 --- a/clippy_lints/src/transmute/missing_transmute_annotations.rs +++ b/clippy_lints/src/transmute/missing_transmute_annotations.rs @@ -50,10 +50,7 @@ pub(super) fn check<'tcx>( } let args = last.args; let missing_generic = match args { - Some(args) if !args.args.is_empty() => args.args.iter().any(|arg| match arg { - GenericArg::Infer(_) => true, - _ => false, - }), + Some(args) if !args.args.is_empty() => args.args.iter().any(|arg| matches!(arg, GenericArg::Infer(_))), _ => true, }; if !missing_generic { diff --git a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs index 3729dfd3e86f..f27aaa2fa77a 100644 --- a/clippy_lints/src/transmute/transmute_int_to_non_zero.rs +++ b/clippy_lints/src/transmute/transmute_int_to_non_zero.rs @@ -24,7 +24,7 @@ pub(super) fn check<'tcx>( if !tcx.is_diagnostic_item(sym::NonZero, adt.did()) { return false; - }; + } let int_ty = substs.type_at(0); if from_ty != int_ty { diff --git a/clippy_lints/src/tuple_array_conversions.rs b/clippy_lints/src/tuple_array_conversions.rs index 99a55f9fc357..008e09dd8bd1 100644 --- a/clippy_lints/src/tuple_array_conversions.rs +++ b/clippy_lints/src/tuple_array_conversions.rs @@ -119,7 +119,7 @@ fn check_tuple<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, elements: & && let LitKind::Int(val, _) = lit.node { return (val == i as u128).then_some(lhs); - }; + } None }) diff --git a/clippy_lints/src/types/borrowed_box.rs b/clippy_lints/src/types/borrowed_box.rs index 1a5fdf0cd64f..2e97772407fd 100644 --- a/clippy_lints/src/types/borrowed_box.rs +++ b/clippy_lints/src/types/borrowed_box.rs @@ -71,7 +71,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m Applicability::Unspecified, ); return true; - }; + } false }, _ => false, diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs new file mode 100644 index 000000000000..efbc536dcb4d --- /dev/null +++ b/clippy_lints/src/unnecessary_semicolon.rs @@ -0,0 +1,109 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::leaks_droppable_temporary_with_limited_lifetime; +use rustc_errors::Applicability; +use rustc_hir::{Block, ExprKind, HirId, MatchSource, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::edition::Edition::Edition2021; + +declare_clippy_lint! { + /// ### What it does + /// Checks for the presence of a semicolon at the end of + /// a `match` or `if` statement evaluating to `()`. + /// + /// ### Why is this bad? + /// The semicolon is not needed, and may be removed to + /// avoid confusion and visual clutter. + /// + /// ### Example + /// ```no_run + /// # let a: u32 = 42; + /// if a > 10 { + /// println!("a is greater than 10"); + /// }; + /// ``` + /// Use instead: + /// ```no_run + /// # let a: u32 = 42; + /// if a > 10 { + /// println!("a is greater than 10"); + /// } + /// ``` + #[clippy::version = "1.86.0"] + pub UNNECESSARY_SEMICOLON, + pedantic, + "unnecessary semicolon after expression returning `()`" +} + +#[derive(Default)] +pub struct UnnecessarySemicolon { + last_statements: Vec, +} + +impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]); + +impl UnnecessarySemicolon { + /// Enter or leave a block, remembering the last statement of the block. + fn handle_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>, enter: bool) { + // Up to edition 2021, removing the semicolon of the last statement of a block + // may result in the scrutinee temporary values to live longer than the block + // variables. To avoid this problem, we do not lint the last statement of an + // expressionless block. + if cx.tcx.sess.edition() <= Edition2021 + && block.expr.is_none() + && let Some(last_stmt) = block.stmts.last() + { + if enter { + self.last_statements.push(last_stmt.hir_id); + } else { + self.last_statements.pop(); + } + } + } + + /// Checks if `stmt` is the last statement in an expressionless block for edition ≤ 2021. + fn is_last_in_block(&self, stmt: &Stmt<'_>) -> bool { + self.last_statements + .last() + .is_some_and(|last_stmt_id| last_stmt_id == &stmt.hir_id) + } +} + +impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { + fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { + self.handle_block(cx, block, true); + } + + fn check_block_post(&mut self, cx: &LateContext<'_>, block: &Block<'_>) { + self.handle_block(cx, block, false); + } + + fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { + // rustfmt already takes care of removing semicolons at the end + // of loops. + if let StmtKind::Semi(expr) = stmt.kind + && !stmt.span.from_expansion() + && !expr.span.from_expansion() + && matches!( + expr.kind, + ExprKind::If(..) | ExprKind::Match(_, _, MatchSource::Normal | MatchSource::Postfix) + ) + && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit + { + if self.is_last_in_block(stmt) && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { + return; + } + + let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi()); + span_lint_and_sugg( + cx, + UNNECESSARY_SEMICOLON, + semi_span, + "unnecessary semicolon", + "remove", + String::new(), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/unneeded_struct_pattern.rs b/clippy_lints/src/unneeded_struct_pattern.rs new file mode 100644 index 000000000000..40ba70d451db --- /dev/null +++ b/clippy_lints/src/unneeded_struct_pattern.rs @@ -0,0 +1,76 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_from_proc_macro; +use rustc_errors::Applicability; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::{Pat, PatKind, QPath}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// Checks for struct patterns that match against unit variant. + /// + /// ### Why is this bad? + /// Struct pattern `{ }` or `{ .. }` is not needed for unit variant. + /// + /// ### Example + /// ```no_run + /// match Some(42) { + /// Some(v) => v, + /// None { .. } => 0, + /// }; + /// // Or + /// match Some(42) { + /// Some(v) => v, + /// None { } => 0, + /// }; + /// ``` + /// Use instead: + /// ```no_run + /// match Some(42) { + /// Some(v) => v, + /// None => 0, + /// }; + /// ``` + #[clippy::version = "1.83.0"] + pub UNNEEDED_STRUCT_PATTERN, + style, + "using struct pattern to match against unit variant" +} + +declare_lint_pass!(UnneededStructPattern => [UNNEEDED_STRUCT_PATTERN]); + +impl LateLintPass<'_> for UnneededStructPattern { + fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) { + if !pat.span.from_expansion() + && let PatKind::Struct(path, [], _) = &pat.kind + && let QPath::Resolved(_, path) = path + && let Res::Def(DefKind::Variant, did) = path.res + { + let enum_did = cx.tcx.parent(did); + let variant = cx.tcx.adt_def(enum_did).variant_with_id(did); + + let has_only_fields_brackets = variant.ctor.is_some() && variant.fields.is_empty(); + let non_exhaustive_activated = !variant.def_id.is_local() && variant.is_field_list_non_exhaustive(); + if !has_only_fields_brackets || non_exhaustive_activated { + return; + } + + if is_from_proc_macro(cx, *path) { + return; + } + + if let Some(brackets_span) = pat.span.trim_start(path.span) { + span_lint_and_sugg( + cx, + UNNEEDED_STRUCT_PATTERN, + brackets_span, + "struct pattern is not needed for a unit variant", + "remove the struct pattern", + String::new(), + Applicability::MachineApplicable, + ); + } + } + } +} diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 7c9455bf8abe..e65123b8a949 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -182,7 +182,7 @@ fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) { emit_lint(cx, expr.span, expr.hir_id, op, &[]); }, _ => {}, - }; + } } fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option { diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 7ffab81a5444..5e452c6d2ac0 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_context}; +use clippy_utils::source::{snippet, snippet_with_context}; use clippy_utils::sugg::{DiagExt as _, Sugg}; use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts}; use clippy_utils::{ @@ -12,6 +12,7 @@ use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::Obligation; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::traits::ObligationCause; +use rustc_middle::ty::adjustment::{Adjust, AutoBorrow, AutoBorrowMutability}; use rustc_middle::ty::{self, AdtDef, EarlyBinder, GenericArg, GenericArgsRef, Ty, TypeVisitableExt}; use rustc_session::impl_lint_pass; use rustc_span::{Span, sym}; @@ -251,26 +252,25 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { // ^^^ let (into_iter_recv, depth) = into_iter_deep_call(cx, into_iter_recv); - let plural = if depth == 0 { "" } else { "s" }; - let mut applicability = Applicability::MachineApplicable; - let sugg = snippet_with_applicability( - cx, - into_iter_recv.span.source_callsite(), - "", - &mut applicability, - ) - .into_owned(); span_lint_and_then( cx, USELESS_CONVERSION, e.span, "explicit call to `.into_iter()` in function argument accepting `IntoIterator`", |diag| { - diag.span_suggestion( - e.span, + let receiver_span = into_iter_recv.span.source_callsite(); + let adjustments = adjustments(cx, into_iter_recv); + let mut sugg = if adjustments.is_empty() { + vec![] + } else { + vec![(receiver_span.shrink_to_lo(), adjustments)] + }; + let plural = if depth == 0 { "" } else { "s" }; + sugg.push((e.span.with_lo(receiver_span.hi()), String::new())); + diag.multipart_suggestion( format!("consider removing the `.into_iter()`{plural}"), sugg, - applicability, + Applicability::MachineApplicable, ); diag.span_note(span, "this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()`"); }, @@ -431,3 +431,16 @@ fn has_eligible_receiver(cx: &LateContext<'_>, recv: &Expr<'_>, expr: &Expr<'_>) } false } + +fn adjustments(cx: &LateContext<'_>, expr: &Expr<'_>) -> String { + let mut prefix = String::new(); + for adj in cx.typeck_results().expr_adjustments(expr) { + match adj.kind { + Adjust::Deref(_) => prefix = format!("*{prefix}"), + Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Mut { .. })) => prefix = format!("&mut {prefix}"), + Adjust::Borrow(AutoBorrow::Ref(AutoBorrowMutability::Not)) => prefix = format!("&{prefix}"), + _ => {}, + } + } + prefix +} diff --git a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs index 49aad881994e..b8bcb9b37560 100644 --- a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs +++ b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs @@ -69,6 +69,6 @@ impl<'tcx> LateLintPass<'tcx> for SlowSymbolComparisons { ), applicability, ); - }; + } } } diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index 0730b561bc29..03c667846b61 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -69,7 +69,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec { }; if self.allow_in_test && is_in_test(cx.tcx, expr.hir_id) { return; - }; + } // the parent callsite of this `vec!` expression, or span to the borrowed one such as `&vec!` let callsite = expr.span.parent_callsite().unwrap_or(expr.span); diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 7fa070cd226b..68b7e1592e2e 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -3,7 +3,7 @@ name = "clippy_utils" # begin autogenerated version version = "0.1.86" # end autogenerated version -edition = "2021" +edition = "2024" description = "Helpful tools for writing lints, provided as they are used in Clippy" repository = "https://github.com/rust-lang/rust-clippy" readme = "README.md" diff --git a/clippy_utils/README.md b/clippy_utils/README.md index c267b804124a..251e3dfe41be 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-01-09 +nightly-2025-01-28 ``` diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index cd6290ced334..179d42a8b5dc 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -400,7 +400,9 @@ fn ty_search_pat(ty: &Ty<'_>) -> (Pat, Pat) { TyKind::OpaqueDef(..) => (Pat::Str("impl"), Pat::Str("")), TyKind::Path(qpath) => qpath_search_pat(&qpath), TyKind::Infer(()) => (Pat::Str("_"), Pat::Str("_")), - TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => (Pat::Str("dyn"), Pat::Str("")), + TyKind::TraitObject(_, tagged_ptr) if let TraitObjectSyntax::Dyn = tagged_ptr.tag() => { + (Pat::Str("dyn"), Pat::Str("")) + }, // NOTE: `TraitObject` is incomplete. It will always return true then. _ => (Pat::Str(""), Pat::Str("")), } diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index d46beddf7312..a660623f4185 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -12,7 +12,9 @@ use rustc_apfloat::ieee::{Half, Quad}; use rustc_ast::ast::{self, LitFloatType, LitKind}; use rustc_data_structures::sync::Lrc; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath, UnOp, PatExpr, PatExprKind}; +use rustc_hir::{ + BinOp, BinOpKind, Block, ConstBlock, Expr, ExprKind, HirId, Item, ItemKind, Node, PatExpr, PatExprKind, QPath, UnOp, +}; use rustc_lexer::tokenize; use rustc_lint::LateContext; use rustc_middle::mir::ConstValue; @@ -451,8 +453,8 @@ impl<'tcx> ConstEvalCtxt<'tcx> { } else { Some(val) } - } - PatExprKind::ConstBlock(ConstBlock { body, ..}) => self.expr(self.tcx.hir().body(*body).value), + }, + PatExprKind::ConstBlock(ConstBlock { body, .. }) => self.expr(self.tcx.hir().body(*body).value), PatExprKind::Path(qpath) => self.qpath(qpath, pat_expr.hir_id), } } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 60be7e4a4d39..6bb876322f24 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -475,7 +475,7 @@ pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) - Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)), _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)), }; - }; + } }, ExprKind::Path(QPath::Resolved(_, path)) if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 9e11a57d1b30..e471dfb6ef19 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -88,7 +88,7 @@ use core::mem; use core::ops::ControlFlow; use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; -use std::iter::{once, repeat}; +use std::iter::{once, repeat_n}; use std::sync::{Mutex, MutexGuard, OnceLock}; use itertools::Itertools; @@ -116,15 +116,15 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc_middle::ty::fast_reject::SimplifiedType; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{ - self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgsRef, IntTy, Ty, TyCtxt, - TypeVisitableExt, UintTy, UpvarCapture, + self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgKind, GenericArgsRef, IntTy, Ty, + TyCtxt, TypeVisitableExt, UintTy, UpvarCapture, }; use rustc_span::hygiene::{ExpnKind, MacroKind}; use rustc_span::source_map::SourceMap; use rustc_span::symbol::{Ident, Symbol, kw}; use rustc_span::{InnerSpan, Span, sym}; use rustc_target::abi::Integer; -use visitors::Visitable; +use visitors::{Visitable, for_each_unconsumed_temporary}; use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; use crate::higher::Range; @@ -814,7 +814,7 @@ fn projection_stack<'a, 'hir>(mut e: &'a Expr<'hir>) -> (Vec<&'a Expr<'hir>>, &' e = ep; }, _ => break e, - }; + } }; result.reverse(); (result, root) @@ -2045,7 +2045,7 @@ pub fn get_async_fn_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'t { return Some(expr); } - }; + } None } @@ -3420,7 +3420,7 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St })) .join("::") } else { - repeat(String::from("super")).take(go_up_by).chain(path).join("::") + repeat_n(String::from("super"), go_up_by).chain(path).join("::") } } @@ -3465,3 +3465,20 @@ pub fn is_receiver_of_method_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool } false } + +/// Returns true if `expr` creates any temporary whose type references a non-static lifetime and has +/// a significant drop and does not consume it. +pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + for_each_unconsumed_temporary(cx, expr, |temporary_ty| { + if temporary_ty.has_significant_drop(cx.tcx, cx.typing_env()) + && temporary_ty + .walk() + .any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(re) if !re.is_static())) + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_break() +} diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 45beb146eb6a..f4c730ef118b 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -251,7 +251,7 @@ impl<'a> PanicExpn<'a> { // This has no argument if name == "panic_cold_explicit" { return Some(Self::Empty); - }; + } let [arg, rest @ ..] = args else { return None; diff --git a/clippy_utils/src/mir/possible_borrower.rs b/clippy_utils/src/mir/possible_borrower.rs index 5eb9b3b8f227..605764cef89f 100644 --- a/clippy_utils/src/mir/possible_borrower.rs +++ b/clippy_utils/src/mir/possible_borrower.rs @@ -184,9 +184,10 @@ impl<'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> { vis.visit_body(mir); vis.into_map(cx) }; - let maybe_storage_live_result = MaybeStorageLive::new(Cow::Owned(DenseBitSet::new_empty(mir.local_decls.len()))) - .iterate_to_fixpoint(cx.tcx, mir, Some("redundant_clone")) - .into_results_cursor(mir); + let maybe_storage_live_result = + MaybeStorageLive::new(Cow::Owned(DenseBitSet::new_empty(mir.local_decls.len()))) + .iterate_to_fixpoint(cx.tcx, mir, Some("redundant_clone")) + .into_results_cursor(mir); let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin); vis.visit_body(mir); vis.into_map(cx, maybe_storage_live_result) diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index 2169a5fdd63b..d73cb7e35611 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -18,10 +18,10 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { - 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY } + 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY, CONST_MUT_REFS, CONST_UNWRAP } 1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP } - 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE } - 1,80,0 { BOX_INTO_ITER } + 1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION } + 1,80,0 { BOX_INTO_ITER, LAZY_CELL } 1,77,0 { C_STR_LITERALS } 1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT } 1,74,0 { REPR_RUST } diff --git a/clippy_utils/src/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs index 2c49df9d807f..bb2a62821100 100644 --- a/clippy_utils/src/numeric_literal.rs +++ b/clippy_utils/src/numeric_literal.rs @@ -125,7 +125,7 @@ impl<'a> NumericLiteral<'a> { integer = &digits[..exp_start]; } else { fraction = Some(&digits[integer.len() + 1..exp_start]); - }; + } exponent = Some((&digits[exp_start..=i], &digits[i + 1..])); break; }, diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 104ae154e369..287bdc9a6fd6 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -29,13 +29,14 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) let def_id = body.source.def_id(); for local in &body.local_decls { - check_ty(tcx, local.ty, local.source_info.span)?; + check_ty(tcx, local.ty, local.source_info.span, msrv)?; } // impl trait is gone in MIR, so check the return type manually check_ty( tcx, tcx.fn_sig(def_id).instantiate_identity().output().skip_binder(), body.local_decls.iter().next().unwrap().source_info.span, + msrv, )?; for bb in &*body.basic_blocks { @@ -51,7 +52,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv) Ok(()) } -fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult { +fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, msrv: &Msrv) -> McfResult { for arg in ty.walk() { let ty = match arg.unpack() { GenericArgKind::Type(ty) => ty, @@ -62,7 +63,7 @@ fn check_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span) -> McfResult { }; match ty.kind() { - ty::Ref(_, _, hir::Mutability::Mut) => { + ty::Ref(_, _, hir::Mutability::Mut) if !msrv.meets(msrvs::CONST_MUT_REFS) => { return Err((span, "mutable references in const fn are unstable".into())); }, ty::Alias(ty::Opaque, ..) => return Err((span, "`impl Trait` in const fn is unstable".into())), diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 32e7c2bbf7cb..f2bbdda70585 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -118,7 +118,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' if contains_ty_adt_constructor_opaque_inner(cx, ty, needle, seen) { return true; } - }; + } }, _ => (), } @@ -1341,3 +1341,14 @@ pub fn get_field_by_name<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, name: Symbol) -> _ => None, } } + +/// Check if `ty` is an `Option` and return its argument type if it is. +pub fn option_arg_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + match ty.kind() { + ty::Adt(adt, args) => cx + .tcx + .is_diagnostic_item(sym::Option, adt.did()) + .then(|| args.type_at(0)), + _ => None, + } +} diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index b5cec31ba9da..3398ff8af2f5 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -55,7 +55,7 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty { && let Some(self_ty_def_id) = adt_def_id(self_ty(cx, method_def_id)) { receiver_type_certainty = receiver_type_certainty.with_def_id(self_ty_def_id); - }; + } let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false); let rhs = if type_is_inferable_from_arguments(cx, expr) { meet( diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index dcc763a8abd7..99984c41714b 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -2,12 +2,11 @@ use crate::ty::needs_ordered_drop; use crate::{get_enclosing_block, path_to_local_id}; use core::ops::ControlFlow; use rustc_ast::visit::{VisitorResult, try_visit}; -use rustc_hir::{self as hir, AmbigArg}; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::intravisit::{self, Visitor, walk_block, walk_expr}; use rustc_hir::{ - AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, LetExpr, Pat, QPath, - Stmt, StructTailExpr, UnOp, UnsafeSource, + self as hir, AmbigArg, AnonConst, Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, + ItemKind, LetExpr, Pat, QPath, Stmt, StructTailExpr, UnOp, UnsafeSource, }; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index b0e4e3e3e573..55e588f5ec73 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -6,7 +6,7 @@ readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-clippy" categories = ["development-tools"] -edition = "2021" +edition = "2024" publish = false default-run = "lintcheck" diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index bd4fcc5e337e..af243f94274d 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -107,7 +107,7 @@ impl LintcheckConfig { } else { std::thread::available_parallelism().map_or(1, NonZero::get) }; - }; + } for lint_name in &mut config.lint_filter { *lint_name = format!( diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index 03e2a24f6f98..e88d9f427bec 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -145,7 +145,7 @@ impl Crate { assert_eq!(status.code(), Some(0)); return Vec::new(); - }; + } if !config.fix { cmd.arg("--message-format=json"); @@ -313,7 +313,7 @@ fn lintcheck(config: LintcheckConfig) { filter }) .collect_into(&mut lint_level_args); - }; + } let crates: Vec = crates .into_iter() diff --git a/rust-toolchain b/rust-toolchain index b1f0a82b1f47..c15d1fe6cd34 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-01-09" +channel = "nightly-2025-01-28" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/rustc_tools_util/Cargo.toml b/rustc_tools_util/Cargo.toml index b63632916ba1..cba025639482 100644 --- a/rustc_tools_util/Cargo.toml +++ b/rustc_tools_util/Cargo.toml @@ -7,6 +7,6 @@ readme = "README.md" license = "MIT OR Apache-2.0" keywords = ["rustc", "tool", "git", "version", "hash"] categories = ["development-tools"] -edition = "2018" +edition = "2024" [dependencies] diff --git a/rustfmt.toml b/rustfmt.toml index 4248f42f654b..0dc6adce7bfc 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,8 +2,8 @@ max_width = 120 comment_width = 100 match_block_trailing_comma = true wrap_comments = true -edition = "2021" +edition = "2024" error_on_line_overflow = true imports_granularity = "Module" -version = "Two" +style_edition = "2024" ignore = ["tests/ui/crashes/ice-10912.rs"] diff --git a/src/driver.rs b/src/driver.rs index 68edefd3095c..8201f332d333 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -223,7 +223,7 @@ pub fn main() { if !has_sysroot_arg(args) { args.extend(vec!["--sysroot".into(), sys_root]); } - }; + } }; // make "clippy-driver --rustc" work like a subcommand that passes further args to "rustc" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index e2e4d92df79f..d1b1a1d23232 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -300,7 +300,9 @@ fn run_ui_cargo(cx: &TestContext) { } fn main() { - set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); + unsafe { + set_var("CLIPPY_DISABLE_DOCS_LINKS", "true"); + } let cx = TestContext::new(); diff --git a/tests/missing-test-files.rs b/tests/missing-test-files.rs index 64eba5e0888a..565dcd73f582 100644 --- a/tests/missing-test-files.rs +++ b/tests/missing-test-files.rs @@ -60,7 +60,7 @@ fn explore_directory(dir: &Path) -> Vec { } }, _ => {}, - }; + } } } } diff --git a/tests/ui-internal/custom_ice_message.stderr b/tests/ui-internal/custom_ice_message.stderr index ff178924bd15..ae5d8ef1d0b5 100644 --- a/tests/ui-internal/custom_ice_message.stderr +++ b/tests/ui-internal/custom_ice_message.stderr @@ -13,5 +13,9 @@ note: rustc running on note: compiler flags: -Z ui-testing -Z deduplicate-diagnostics=no +query stack during panic: +#0 [early_lint_checks] perform lints prior to macro expansion +#1 [hir_crate] getting the crate HIR +... and 3 other queries... use `env RUST_BACKTRACE=1` to see the full query stack note: Clippy version: foo diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 3f2040730851..f09106773c7e 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -170,6 +170,7 @@ pub fn hard_coded_allowed() { let _ = Saturating(0u32) + Saturating(0u32); let _ = String::new() + ""; + let _ = String::new() + &String::new(); let _ = Wrapping(0u32) + Wrapping(0u32); let saturating: Saturating = Saturating(0u32); @@ -408,11 +409,14 @@ pub fn unknown_ops_or_runtime_ops_that_can_overflow() { _n.wrapping_rem(_n); _n.wrapping_rem_euclid(_n); + _n.saturating_div(*Box::new(_n)); + // Unary _n = -_n; _n = -&_n; _custom = -_custom; _custom = -&_custom; + _ = -*Box::new(_n); } // Copied and pasted from the `integer_arithmetic` lint for comparison. @@ -534,4 +538,11 @@ pub fn issue_12318() { one.sub_assign(1); } +pub fn explicit_methods() { + use core::ops::Add; + let one: i32 = 1; + one.add(&one); + Box::new(one).add(one); +} + fn main() {} diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 78b1aca4b8a4..9b4cfb83fbb2 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -14,730 +14,760 @@ LL | let _ = 1f128 + 1f128; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:307:5 + --> tests/ui/arithmetic_side_effects.rs:173:13 + | +LL | let _ = String::new() + &String::new(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:308:5 | LL | _n += 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:308:5 + --> tests/ui/arithmetic_side_effects.rs:309:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:309:5 + --> tests/ui/arithmetic_side_effects.rs:310:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:310:5 + --> tests/ui/arithmetic_side_effects.rs:311:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:311:5 + --> tests/ui/arithmetic_side_effects.rs:312:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:312:5 + --> tests/ui/arithmetic_side_effects.rs:313:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:313:5 + --> tests/ui/arithmetic_side_effects.rs:314:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:314:5 + --> tests/ui/arithmetic_side_effects.rs:315:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:315:5 + --> tests/ui/arithmetic_side_effects.rs:316:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:316:5 + --> tests/ui/arithmetic_side_effects.rs:317:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:317:5 + --> tests/ui/arithmetic_side_effects.rs:318:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:318:5 + --> tests/ui/arithmetic_side_effects.rs:319:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:319:5 + --> tests/ui/arithmetic_side_effects.rs:320:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:320:5 + --> tests/ui/arithmetic_side_effects.rs:321:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:321:5 + --> tests/ui/arithmetic_side_effects.rs:322:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:322:5 + --> tests/ui/arithmetic_side_effects.rs:323:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:323:5 + --> tests/ui/arithmetic_side_effects.rs:324:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:324:5 + --> tests/ui/arithmetic_side_effects.rs:325:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:325:5 + --> tests/ui/arithmetic_side_effects.rs:326:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:326:5 + --> tests/ui/arithmetic_side_effects.rs:327:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:327:5 + --> tests/ui/arithmetic_side_effects.rs:328:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:328:5 + --> tests/ui/arithmetic_side_effects.rs:329:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:329:5 + --> tests/ui/arithmetic_side_effects.rs:330:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:330:5 + --> tests/ui/arithmetic_side_effects.rs:331:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:331:5 + --> tests/ui/arithmetic_side_effects.rs:332:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:332:5 + --> tests/ui/arithmetic_side_effects.rs:333:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:333:5 + --> tests/ui/arithmetic_side_effects.rs:334:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:334:5 + --> tests/ui/arithmetic_side_effects.rs:335:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:335:5 + --> tests/ui/arithmetic_side_effects.rs:336:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:336:5 + --> tests/ui/arithmetic_side_effects.rs:337:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:337:5 + --> tests/ui/arithmetic_side_effects.rs:338:5 | LL | _custom >>= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:338:5 + --> tests/ui/arithmetic_side_effects.rs:339:5 | LL | _custom >>= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:339:5 + --> tests/ui/arithmetic_side_effects.rs:340:5 | LL | _custom <<= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:340:5 + --> tests/ui/arithmetic_side_effects.rs:341:5 | LL | _custom <<= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:341:5 + --> tests/ui/arithmetic_side_effects.rs:342:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:342:5 + --> tests/ui/arithmetic_side_effects.rs:343:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:343:5 + --> tests/ui/arithmetic_side_effects.rs:344:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:344:5 + --> tests/ui/arithmetic_side_effects.rs:345:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:345:5 + --> tests/ui/arithmetic_side_effects.rs:346:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:346:5 + --> tests/ui/arithmetic_side_effects.rs:347:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:347:5 + --> tests/ui/arithmetic_side_effects.rs:348:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:348:5 + --> tests/ui/arithmetic_side_effects.rs:349:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:349:5 + --> tests/ui/arithmetic_side_effects.rs:350:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:350:5 + --> tests/ui/arithmetic_side_effects.rs:351:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:351:5 + --> tests/ui/arithmetic_side_effects.rs:352:5 | LL | _custom >>= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:352:5 + --> tests/ui/arithmetic_side_effects.rs:353:5 | LL | _custom >>= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:353:5 + --> tests/ui/arithmetic_side_effects.rs:354:5 | LL | _custom <<= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:354:5 + --> tests/ui/arithmetic_side_effects.rs:355:5 | LL | _custom <<= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:357:10 + --> tests/ui/arithmetic_side_effects.rs:358:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:358:10 + --> tests/ui/arithmetic_side_effects.rs:359:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:359:10 + --> tests/ui/arithmetic_side_effects.rs:360:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:360:10 + --> tests/ui/arithmetic_side_effects.rs:361:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:361:10 + --> tests/ui/arithmetic_side_effects.rs:362:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:362:10 + --> tests/ui/arithmetic_side_effects.rs:363:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:363:10 + --> tests/ui/arithmetic_side_effects.rs:364:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:364:10 + --> tests/ui/arithmetic_side_effects.rs:365:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:365:10 + --> tests/ui/arithmetic_side_effects.rs:366:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:366:10 + --> tests/ui/arithmetic_side_effects.rs:367:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:367:10 + --> tests/ui/arithmetic_side_effects.rs:368:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:368:10 + --> tests/ui/arithmetic_side_effects.rs:369:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:369:10 + --> tests/ui/arithmetic_side_effects.rs:370:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:370:10 + --> tests/ui/arithmetic_side_effects.rs:371:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:371:10 + --> tests/ui/arithmetic_side_effects.rs:372:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:372:10 + --> tests/ui/arithmetic_side_effects.rs:373:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:373:10 + --> tests/ui/arithmetic_side_effects.rs:374:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:374:10 + --> tests/ui/arithmetic_side_effects.rs:375:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:375:10 + --> tests/ui/arithmetic_side_effects.rs:376:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:376:15 + --> tests/ui/arithmetic_side_effects.rs:377:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:377:15 + --> tests/ui/arithmetic_side_effects.rs:378:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:378:15 + --> tests/ui/arithmetic_side_effects.rs:379:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:379:15 + --> tests/ui/arithmetic_side_effects.rs:380:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:380:15 + --> tests/ui/arithmetic_side_effects.rs:381:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:381:15 + --> tests/ui/arithmetic_side_effects.rs:382:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:382:15 + --> tests/ui/arithmetic_side_effects.rs:383:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:383:15 + --> tests/ui/arithmetic_side_effects.rs:384:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:384:15 + --> tests/ui/arithmetic_side_effects.rs:385:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:385:15 + --> tests/ui/arithmetic_side_effects.rs:386:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:386:15 + --> tests/ui/arithmetic_side_effects.rs:387:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:387:15 + --> tests/ui/arithmetic_side_effects.rs:388:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:388:15 + --> tests/ui/arithmetic_side_effects.rs:389:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:389:15 + --> tests/ui/arithmetic_side_effects.rs:390:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:390:15 + --> tests/ui/arithmetic_side_effects.rs:391:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:391:15 + --> tests/ui/arithmetic_side_effects.rs:392:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:392:15 + --> tests/ui/arithmetic_side_effects.rs:393:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:393:15 + --> tests/ui/arithmetic_side_effects.rs:394:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:394:15 + --> tests/ui/arithmetic_side_effects.rs:395:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:395:15 + --> tests/ui/arithmetic_side_effects.rs:396:15 | LL | _custom = _custom >> _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:396:15 + --> tests/ui/arithmetic_side_effects.rs:397:15 | LL | _custom = _custom >> &_custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:397:15 + --> tests/ui/arithmetic_side_effects.rs:398:15 | LL | _custom = Custom << _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:398:15 + --> tests/ui/arithmetic_side_effects.rs:399:15 | LL | _custom = &Custom << _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:401:23 + --> tests/ui/arithmetic_side_effects.rs:402:23 | LL | _n.saturating_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:402:21 + --> tests/ui/arithmetic_side_effects.rs:403:21 | LL | _n.wrapping_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:403:21 + --> tests/ui/arithmetic_side_effects.rs:404:21 | LL | _n.wrapping_rem(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:404:28 + --> tests/ui/arithmetic_side_effects.rs:405:28 | LL | _n.wrapping_rem_euclid(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:406:23 + --> tests/ui/arithmetic_side_effects.rs:407:23 | LL | _n.saturating_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:407:21 + --> tests/ui/arithmetic_side_effects.rs:408:21 | LL | _n.wrapping_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:408:21 + --> tests/ui/arithmetic_side_effects.rs:409:21 | LL | _n.wrapping_rem(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:409:28 + --> tests/ui/arithmetic_side_effects.rs:410:28 | LL | _n.wrapping_rem_euclid(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:412:10 + --> tests/ui/arithmetic_side_effects.rs:412:23 + | +LL | _n.saturating_div(*Box::new(_n)); + | ^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:415:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:413:10 + --> tests/ui/arithmetic_side_effects.rs:416:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:414:15 + --> tests/ui/arithmetic_side_effects.rs:417:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:415:15 + --> tests/ui/arithmetic_side_effects.rs:418:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:424:5 + --> tests/ui/arithmetic_side_effects.rs:419:9 + | +LL | _ = -*Box::new(_n); + | ^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:428:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:425:5 + --> tests/ui/arithmetic_side_effects.rs:429:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:426:5 + --> tests/ui/arithmetic_side_effects.rs:430:5 | LL | 1 % i / 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:427:5 + --> tests/ui/arithmetic_side_effects.rs:431:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:428:5 + --> tests/ui/arithmetic_side_effects.rs:432:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:439:5 + --> tests/ui/arithmetic_side_effects.rs:443:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:440:5 + --> tests/ui/arithmetic_side_effects.rs:444:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:441:5 + --> tests/ui/arithmetic_side_effects.rs:445:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:443:5 + --> tests/ui/arithmetic_side_effects.rs:447:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:445:5 + --> tests/ui/arithmetic_side_effects.rs:449:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:446:5 + --> tests/ui/arithmetic_side_effects.rs:450:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:448:5 + --> tests/ui/arithmetic_side_effects.rs:452:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:450:5 + --> tests/ui/arithmetic_side_effects.rs:454:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:451:5 + --> tests/ui/arithmetic_side_effects.rs:455:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:461:5 + --> tests/ui/arithmetic_side_effects.rs:465:5 | LL | 10 / a | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:515:9 + --> tests/ui/arithmetic_side_effects.rs:519:9 | LL | x / maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:519:9 + --> tests/ui/arithmetic_side_effects.rs:523:9 | LL | x % maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:530:5 + --> tests/ui/arithmetic_side_effects.rs:534:5 | LL | one.add_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:534:5 + --> tests/ui/arithmetic_side_effects.rs:538:5 | LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ -error: aborting due to 123 previous errors +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:544:5 + | +LL | one.add(&one); + | ^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:545:5 + | +LL | Box::new(one).add(one); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 128 previous errors diff --git a/tests/ui/auxiliary/non-exhaustive-enum.rs b/tests/ui/auxiliary/non-exhaustive-enum.rs index 420232f9f8d8..e3205193ccea 100644 --- a/tests/ui/auxiliary/non-exhaustive-enum.rs +++ b/tests/ui/auxiliary/non-exhaustive-enum.rs @@ -6,3 +6,24 @@ pub enum ErrorKind { #[doc(hidden)] Uncategorized, } + +#[non_exhaustive] +pub enum ExtNonExhaustiveEnum { + Unit, + Tuple(i32), + Struct { field: i32 }, +} + +pub enum ExtNonExhaustiveVariant { + ExhaustiveUnit, + #[non_exhaustive] + Unit, + #[non_exhaustive] + Tuple(i32), + #[non_exhaustive] + StructNoField {}, + #[non_exhaustive] + Struct { + field: i32, + }, +} diff --git a/tests/ui/bytes_nth.fixed b/tests/ui/bytes_nth.fixed index 11deb2390fd4..da35fcb55e5a 100644 --- a/tests/ui/bytes_nth.fixed +++ b/tests/ui/bytes_nth.fixed @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_operation)] +#![allow(clippy::sliced_string_as_bytes)] #![warn(clippy::bytes_nth)] fn main() { diff --git a/tests/ui/bytes_nth.rs b/tests/ui/bytes_nth.rs index 62d9c7a5ea79..5dbe84ecec8b 100644 --- a/tests/ui/bytes_nth.rs +++ b/tests/ui/bytes_nth.rs @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_operation)] +#![allow(clippy::sliced_string_as_bytes)] #![warn(clippy::bytes_nth)] fn main() { diff --git a/tests/ui/bytes_nth.stderr b/tests/ui/bytes_nth.stderr index c6f21576c3db..c5f341cb37f6 100644 --- a/tests/ui/bytes_nth.stderr +++ b/tests/ui/bytes_nth.stderr @@ -1,5 +1,5 @@ error: called `.bytes().nth()` on a `String` - --> tests/ui/bytes_nth.rs:6:13 + --> tests/ui/bytes_nth.rs:7:13 | LL | let _ = s.bytes().nth(3); | ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3).copied()` @@ -8,13 +8,13 @@ LL | let _ = s.bytes().nth(3); = help: to override `-D warnings` add `#[allow(clippy::bytes_nth)]` error: called `.bytes().nth().unwrap()` on a `String` - --> tests/ui/bytes_nth.rs:7:14 + --> tests/ui/bytes_nth.rs:8:14 | LL | let _ = &s.bytes().nth(3).unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.as_bytes()[3]` error: called `.bytes().nth()` on a `str` - --> tests/ui/bytes_nth.rs:8:13 + --> tests/ui/bytes_nth.rs:9:13 | LL | let _ = s[..].bytes().nth(3); | ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3).copied()` diff --git a/tests/ui/crashes/ice-11230.fixed b/tests/ui/crashes/ice-11230.fixed new file mode 100644 index 000000000000..1d4c3dd9dcc4 --- /dev/null +++ b/tests/ui/crashes/ice-11230.fixed @@ -0,0 +1,16 @@ +// Test for https://github.com/rust-lang/rust-clippy/issues/11230 +#![warn(clippy::explicit_iter_loop)] +#![warn(clippy::needless_collect)] + +// explicit_iter_loop +fn main() { + const A: &[for<'a> fn(&'a ())] = &[]; + for v in A {} +} + +// needless_collect +trait Helper<'a>: Iterator {} + +fn x(w: &mut dyn for<'a> Helper<'a>) { + w.next().is_none(); +} diff --git a/tests/ui/crashes/ice-11230.rs b/tests/ui/crashes/ice-11230.rs index 94044e9435ed..a16fb271497c 100644 --- a/tests/ui/crashes/ice-11230.rs +++ b/tests/ui/crashes/ice-11230.rs @@ -1,6 +1,16 @@ // Test for https://github.com/rust-lang/rust-clippy/issues/11230 +#![warn(clippy::explicit_iter_loop)] +#![warn(clippy::needless_collect)] +// explicit_iter_loop fn main() { const A: &[for<'a> fn(&'a ())] = &[]; for v in A.iter() {} } + +// needless_collect +trait Helper<'a>: Iterator {} + +fn x(w: &mut dyn for<'a> Helper<'a>) { + w.collect::>().is_empty(); +} diff --git a/tests/ui/crashes/ice-11230.stderr b/tests/ui/crashes/ice-11230.stderr new file mode 100644 index 000000000000..7167d90e456e --- /dev/null +++ b/tests/ui/crashes/ice-11230.stderr @@ -0,0 +1,20 @@ +error: it is more concise to loop over references to containers instead of using explicit iteration methods + --> tests/ui/crashes/ice-11230.rs:8:14 + | +LL | for v in A.iter() {} + | ^^^^^^^^ help: to write this more concisely, try: `A` + | + = note: `-D clippy::explicit-iter-loop` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::explicit_iter_loop)]` + +error: avoid using `collect()` when not needed + --> tests/ui/crashes/ice-11230.rs:15:7 + | +LL | w.collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + | + = note: `-D clippy::needless-collect` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_collect)]` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/crashes/ice-11422.fixed b/tests/ui/crashes/ice-11422.fixed index ca5721cbb2ba..d996b1db08a6 100644 --- a/tests/ui/crashes/ice-11422.fixed +++ b/tests/ui/crashes/ice-11422.fixed @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::ops::*; -fn gen() -> impl PartialOrd + Debug {} +fn r#gen() -> impl PartialOrd + Debug {} struct Bar {} trait Foo {} diff --git a/tests/ui/crashes/ice-11422.rs b/tests/ui/crashes/ice-11422.rs index 355ec2480bba..eb89b7c38f43 100644 --- a/tests/ui/crashes/ice-11422.rs +++ b/tests/ui/crashes/ice-11422.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use std::ops::*; -fn gen() -> impl PartialOrd + PartialEq + Debug {} +fn r#gen() -> impl PartialOrd + PartialEq + Debug {} struct Bar {} trait Foo {} diff --git a/tests/ui/crashes/ice-11422.stderr b/tests/ui/crashes/ice-11422.stderr index a340977f4699..67944e4e6e80 100644 --- a/tests/ui/crashes/ice-11422.stderr +++ b/tests/ui/crashes/ice-11422.stderr @@ -1,15 +1,15 @@ error: this bound is already specified as the supertrait of `PartialOrd` - --> tests/ui/crashes/ice-11422.rs:6:31 + --> tests/ui/crashes/ice-11422.rs:6:33 | -LL | fn gen() -> impl PartialOrd + PartialEq + Debug {} - | ^^^^^^^^^ +LL | fn r#gen() -> impl PartialOrd + PartialEq + Debug {} + | ^^^^^^^^^ | = note: `-D clippy::implied-bounds-in-impls` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::implied_bounds_in_impls)]` help: try removing this bound | -LL - fn gen() -> impl PartialOrd + PartialEq + Debug {} -LL + fn gen() -> impl PartialOrd + Debug {} +LL - fn r#gen() -> impl PartialOrd + PartialEq + Debug {} +LL + fn r#gen() -> impl PartialOrd + Debug {} | error: aborting due to 1 previous error diff --git a/tests/ui/doc/doc_lazy_list.fixed b/tests/ui/doc/doc_lazy_list.fixed index 0822cc7c6350..8e2ed1bbd18d 100644 --- a/tests/ui/doc/doc_lazy_list.fixed +++ b/tests/ui/doc/doc_lazy_list.fixed @@ -1,4 +1,5 @@ #![warn(clippy::doc_lazy_continuation)] +#![allow(clippy::doc_overindented_list_items)] /// 1. nest here /// lazy continuation diff --git a/tests/ui/doc/doc_lazy_list.rs b/tests/ui/doc/doc_lazy_list.rs index 068de140e00a..1da11d8fae26 100644 --- a/tests/ui/doc/doc_lazy_list.rs +++ b/tests/ui/doc/doc_lazy_list.rs @@ -1,4 +1,5 @@ #![warn(clippy::doc_lazy_continuation)] +#![allow(clippy::doc_overindented_list_items)] /// 1. nest here /// lazy continuation diff --git a/tests/ui/doc/doc_lazy_list.stderr b/tests/ui/doc/doc_lazy_list.stderr index b38f43b7555f..cea6157119f7 100644 --- a/tests/ui/doc/doc_lazy_list.stderr +++ b/tests/ui/doc/doc_lazy_list.stderr @@ -1,5 +1,5 @@ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:4:5 + --> tests/ui/doc/doc_lazy_list.rs:5:5 | LL | /// lazy continuation | ^ @@ -13,7 +13,7 @@ LL | /// lazy continuation | +++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:9:5 + --> tests/ui/doc/doc_lazy_list.rs:10:5 | LL | /// lazy list continuations don't make warnings with this lint | ^ @@ -25,7 +25,7 @@ LL | /// lazy list continuations don't make warnings with this lint | +++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:11:5 + --> tests/ui/doc/doc_lazy_list.rs:12:5 | LL | /// because they don't have the | ^ @@ -37,7 +37,7 @@ LL | /// because they don't have the | +++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:16:5 + --> tests/ui/doc/doc_lazy_list.rs:17:5 | LL | /// lazy continuation | ^ @@ -49,7 +49,7 @@ LL | /// lazy continuation | ++++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:21:5 + --> tests/ui/doc/doc_lazy_list.rs:22:5 | LL | /// lazy list continuations don't make warnings with this lint | ^ @@ -61,7 +61,7 @@ LL | /// lazy list continuations don't make warnings with this lint | ++++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:23:5 + --> tests/ui/doc/doc_lazy_list.rs:24:5 | LL | /// because they don't have the | ^ @@ -73,7 +73,7 @@ LL | /// because they don't have the | ++++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:28:5 + --> tests/ui/doc/doc_lazy_list.rs:29:5 | LL | /// lazy continuation | ^ @@ -85,7 +85,7 @@ LL | /// lazy continuation | ++++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:33:5 + --> tests/ui/doc/doc_lazy_list.rs:34:5 | LL | /// this will warn on the lazy continuation | ^ @@ -97,7 +97,7 @@ LL | /// this will warn on the lazy continuation | ++++++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:35:5 + --> tests/ui/doc/doc_lazy_list.rs:36:5 | LL | /// and so should this | ^^^^ @@ -109,7 +109,7 @@ LL | /// and so should this | ++ error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:56:5 + --> tests/ui/doc/doc_lazy_list.rs:57:5 | LL | /// 'protocol_descriptors': [ | ^ @@ -121,7 +121,7 @@ LL | /// 'protocol_descriptors': [ | + error: doc list item without indentation - --> tests/ui/doc/doc_lazy_list.rs:75:5 + --> tests/ui/doc/doc_lazy_list.rs:76:5 | LL | /// ] | ^ diff --git a/tests/ui/doc/doc_overindented_list_items.fixed b/tests/ui/doc/doc_overindented_list_items.fixed new file mode 100644 index 000000000000..940cff48c1e9 --- /dev/null +++ b/tests/ui/doc/doc_overindented_list_items.fixed @@ -0,0 +1,28 @@ +#![warn(clippy::doc_overindented_list_items)] + +#[rustfmt::skip] +/// - first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// - second list item +fn foo() {} + +#[rustfmt::skip] +/// - first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// - second list item +fn bar() {} + +#[rustfmt::skip] +/// * first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// * second list item +fn baz() {} diff --git a/tests/ui/doc/doc_overindented_list_items.rs b/tests/ui/doc/doc_overindented_list_items.rs new file mode 100644 index 000000000000..77f3ee8a64d4 --- /dev/null +++ b/tests/ui/doc/doc_overindented_list_items.rs @@ -0,0 +1,28 @@ +#![warn(clippy::doc_overindented_list_items)] + +#[rustfmt::skip] +/// - first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// - second list item +fn foo() {} + +#[rustfmt::skip] +/// - first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// - second list item +fn bar() {} + +#[rustfmt::skip] +/// * first list item +/// overindented line +//~^ ERROR: doc list item overindented +/// this is overindented line too +//~^ ERROR: doc list item overindented +/// * second list item +fn baz() {} diff --git a/tests/ui/doc/doc_overindented_list_items.stderr b/tests/ui/doc/doc_overindented_list_items.stderr new file mode 100644 index 000000000000..ff201ba5eb94 --- /dev/null +++ b/tests/ui/doc/doc_overindented_list_items.stderr @@ -0,0 +1,41 @@ +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:5:5 + | +LL | /// overindented line + | ^^^^^^^ help: try using ` ` (2 spaces) + | + = note: `-D clippy::doc-overindented-list-items` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_overindented_list_items)]` + +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:7:5 + | +LL | /// this is overindented line too + | ^^^^^ help: try using ` ` (2 spaces) + +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:14:7 + | +LL | /// overindented line + | ^^^^^ help: try using ` ` (2 spaces) + +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:16:7 + | +LL | /// this is overindented line too + | ^^^ help: try using ` ` (2 spaces) + +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:23:5 + | +LL | /// overindented line + | ^^^^^^^ help: try using ` ` (2 spaces) + +error: doc list item overindented + --> tests/ui/doc/doc_overindented_list_items.rs:25:5 + | +LL | /// this is overindented line too + | ^^^^^ help: try using ` ` (2 spaces) + +error: aborting due to 6 previous errors + diff --git a/tests/ui/drain_collect_nostd.fixed b/tests/ui/drain_collect_nostd.fixed new file mode 100644 index 000000000000..a4ab2956f2a6 --- /dev/null +++ b/tests/ui/drain_collect_nostd.fixed @@ -0,0 +1,8 @@ +#![warn(clippy::drain_collect)] +#![no_std] +extern crate alloc; +use alloc::vec::Vec; + +fn remove_all(v: &mut Vec) -> Vec { + core::mem::take(v) +} diff --git a/tests/ui/drain_collect_nostd.rs b/tests/ui/drain_collect_nostd.rs new file mode 100644 index 000000000000..a8be1ce6bbd3 --- /dev/null +++ b/tests/ui/drain_collect_nostd.rs @@ -0,0 +1,8 @@ +#![warn(clippy::drain_collect)] +#![no_std] +extern crate alloc; +use alloc::vec::Vec; + +fn remove_all(v: &mut Vec) -> Vec { + v.drain(..).collect() +} diff --git a/tests/ui/drain_collect_nostd.stderr b/tests/ui/drain_collect_nostd.stderr new file mode 100644 index 000000000000..91b38932fee7 --- /dev/null +++ b/tests/ui/drain_collect_nostd.stderr @@ -0,0 +1,11 @@ +error: you seem to be trying to move all elements into a new `Vec` + --> tests/ui/drain_collect_nostd.rs:7:5 + | +LL | v.drain(..).collect() + | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `mem::take`: `core::mem::take(v)` + | + = note: `-D clippy::drain-collect` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::drain_collect)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/eta_nostd.fixed b/tests/ui/eta_nostd.fixed new file mode 100644 index 000000000000..23059c52b67f --- /dev/null +++ b/tests/ui/eta_nostd.fixed @@ -0,0 +1,10 @@ +#![warn(clippy::redundant_closure)] +#![no_std] + +extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; + +fn issue_13895() { + let _: Option> = true.then(alloc::vec::Vec::new); +} diff --git a/tests/ui/eta_nostd.rs b/tests/ui/eta_nostd.rs new file mode 100644 index 000000000000..ae44ac348c64 --- /dev/null +++ b/tests/ui/eta_nostd.rs @@ -0,0 +1,10 @@ +#![warn(clippy::redundant_closure)] +#![no_std] + +extern crate alloc; +use alloc::vec; +use alloc::vec::Vec; + +fn issue_13895() { + let _: Option> = true.then(|| vec![]); +} diff --git a/tests/ui/eta_nostd.stderr b/tests/ui/eta_nostd.stderr new file mode 100644 index 000000000000..4dfef43efa47 --- /dev/null +++ b/tests/ui/eta_nostd.stderr @@ -0,0 +1,11 @@ +error: redundant closure + --> tests/ui/eta_nostd.rs:9:40 + | +LL | let _: Option> = true.then(|| vec![]); + | ^^^^^^^^^ help: replace the closure with `Vec::new`: `alloc::vec::Vec::new` + | + = note: `-D clippy::redundant-closure` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/from_iter_instead_of_collect.fixed b/tests/ui/from_iter_instead_of_collect.fixed index c250162dfb8c..67da45a348f9 100644 --- a/tests/ui/from_iter_instead_of_collect.fixed +++ b/tests/ui/from_iter_instead_of_collect.fixed @@ -1,6 +1,6 @@ #![warn(clippy::from_iter_instead_of_collect)] #![allow(unused_imports)] -#![allow(clippy::useless_vec)] +#![allow(clippy::useless_vec, clippy::manual_repeat_n)] use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; diff --git a/tests/ui/from_iter_instead_of_collect.rs b/tests/ui/from_iter_instead_of_collect.rs index 8adbb841c8ba..423a7454bed7 100644 --- a/tests/ui/from_iter_instead_of_collect.rs +++ b/tests/ui/from_iter_instead_of_collect.rs @@ -1,6 +1,6 @@ #![warn(clippy::from_iter_instead_of_collect)] #![allow(unused_imports)] -#![allow(clippy::useless_vec)] +#![allow(clippy::useless_vec, clippy::manual_repeat_n)] use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque}; diff --git a/tests/ui/implicit_hasher.fixed b/tests/ui/implicit_hasher.fixed index 2d6dc0274cf2..971746ae95d7 100644 --- a/tests/ui/implicit_hasher.fixed +++ b/tests/ui/implicit_hasher.fixed @@ -70,7 +70,7 @@ pub fn map(map: &mut HashMap) {} pub fn set(set: &mut HashSet) {} #[inline_macros] -pub mod gen { +pub mod gen_ { use super::*; inline! { impl Foo for HashMap { diff --git a/tests/ui/implicit_hasher.rs b/tests/ui/implicit_hasher.rs index 0a334357bd1b..b34aa1f81374 100644 --- a/tests/ui/implicit_hasher.rs +++ b/tests/ui/implicit_hasher.rs @@ -70,7 +70,7 @@ pub fn map(map: &mut HashMap) {} pub fn set(set: &mut HashSet) {} #[inline_macros] -pub mod gen { +pub mod gen_ { use super::*; inline! { impl Foo for HashMap { diff --git a/tests/ui/implicit_hasher.stderr b/tests/ui/implicit_hasher.stderr index 48c6ebc209cf..442f4789aacf 100644 --- a/tests/ui/implicit_hasher.stderr +++ b/tests/ui/implicit_hasher.stderr @@ -98,7 +98,7 @@ error: impl for `HashMap` should be generalized over different hashers LL | impl Foo for HashMap { | ^^^^^^^^^^^^^ | - = note: this error originates in the macro `__inline_mac_mod_gen` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__inline_mac_mod_gen_` (in Nightly builds, run with -Z macro-backtrace for more info) help: add a type parameter for `BuildHasher` | LL ~ impl Foo for HashMap { diff --git a/tests/ui/manual_div_ceil.fixed b/tests/ui/manual_div_ceil.fixed index 1fb1df5b4425..f6eb5a9784ac 100644 --- a/tests/ui/manual_div_ceil.fixed +++ b/tests/ui/manual_div_ceil.fixed @@ -50,3 +50,14 @@ fn issue_13843() { let _ = 1_000_000_u32.div_ceil(6u32); } + +fn issue_13950() { + let x = 33u32; + let _ = x.div_ceil(8); + let _ = x.div_ceil(8); + + let y = -33i32; + let _ = (y + -8) / -7; + let _ = (-8 + y) / -7; + let _ = (y - 8) / -7; +} diff --git a/tests/ui/manual_div_ceil.rs b/tests/ui/manual_div_ceil.rs index 4f6d38f0d145..2f063afe787d 100644 --- a/tests/ui/manual_div_ceil.rs +++ b/tests/ui/manual_div_ceil.rs @@ -50,3 +50,14 @@ fn issue_13843() { let _ = (1_000_000 + 6u32 - 1) / 6u32; } + +fn issue_13950() { + let x = 33u32; + let _ = (x + 7) / 8; + let _ = (7 + x) / 8; + + let y = -33i32; + let _ = (y + -8) / -7; + let _ = (-8 + y) / -7; + let _ = (y - 8) / -7; +} diff --git a/tests/ui/manual_div_ceil.stderr b/tests/ui/manual_div_ceil.stderr index 3d87fe8e0409..0bac5d8ef1c1 100644 --- a/tests/ui/manual_div_ceil.stderr +++ b/tests/ui/manual_div_ceil.stderr @@ -85,5 +85,17 @@ error: manually reimplementing `div_ceil` LL | let _ = (1_000_000 + 6u32 - 1) / 6u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `1_000_000_u32.div_ceil(6u32)` -error: aborting due to 14 previous errors +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:56:13 + | +LL | let _ = (x + 7) / 8; + | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil.rs:57:13 + | +LL | let _ = (7 + x) / 8; + | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: aborting due to 16 previous errors diff --git a/tests/ui/manual_div_ceil_with_feature.fixed b/tests/ui/manual_div_ceil_with_feature.fixed index f32b78aa14d0..01c58151bc94 100644 --- a/tests/ui/manual_div_ceil_with_feature.fixed +++ b/tests/ui/manual_div_ceil_with_feature.fixed @@ -50,3 +50,14 @@ fn issue_13843() { let _ = 1_000_000_u32.div_ceil(6u32); } + +fn issue_13950() { + let x = 33u32; + let _ = x.div_ceil(8); + let _ = x.div_ceil(8); + + let y = -33i32; + let _ = y.div_ceil(-7); + let _ = y.div_ceil(-7); + let _ = y.div_ceil(-7); +} diff --git a/tests/ui/manual_div_ceil_with_feature.rs b/tests/ui/manual_div_ceil_with_feature.rs index 54d89fcbd462..048ff401581f 100644 --- a/tests/ui/manual_div_ceil_with_feature.rs +++ b/tests/ui/manual_div_ceil_with_feature.rs @@ -50,3 +50,14 @@ fn issue_13843() { let _ = (1_000_000 + 6u32 - 1) / 6u32; } + +fn issue_13950() { + let x = 33u32; + let _ = (x + 7) / 8; + let _ = (7 + x) / 8; + + let y = -33i32; + let _ = (y + -8) / -7; + let _ = (-8 + y) / -7; + let _ = (y - 8) / -7; +} diff --git a/tests/ui/manual_div_ceil_with_feature.stderr b/tests/ui/manual_div_ceil_with_feature.stderr index c5e8c1a687cd..807cfd82724e 100644 --- a/tests/ui/manual_div_ceil_with_feature.stderr +++ b/tests/ui/manual_div_ceil_with_feature.stderr @@ -109,5 +109,35 @@ error: manually reimplementing `div_ceil` LL | let _ = (1_000_000 + 6u32 - 1) / 6u32; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `1_000_000_u32.div_ceil(6u32)` -error: aborting due to 18 previous errors +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:56:13 + | +LL | let _ = (x + 7) / 8; + | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:57:13 + | +LL | let _ = (7 + x) / 8; + | ^^^^^^^^^^^ help: consider using `.div_ceil()`: `x.div_ceil(8)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:60:13 + | +LL | let _ = (y + -8) / -7; + | ^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(-7)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:61:13 + | +LL | let _ = (-8 + y) / -7; + | ^^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(-7)` + +error: manually reimplementing `div_ceil` + --> tests/ui/manual_div_ceil_with_feature.rs:62:13 + | +LL | let _ = (y - 8) / -7; + | ^^^^^^^^^^^^ help: consider using `.div_ceil()`: `y.div_ceil(-7)` + +error: aborting due to 23 previous errors diff --git a/tests/ui/manual_ok_err.fixed b/tests/ui/manual_ok_err.fixed new file mode 100644 index 000000000000..e7e0464c4787 --- /dev/null +++ b/tests/ui/manual_ok_err.fixed @@ -0,0 +1,91 @@ +#![warn(clippy::manual_ok_err)] + +fn funcall() -> Result { + todo!() +} + +fn main() { + let _ = funcall().ok(); + + let _ = funcall().ok(); + + let _ = funcall().err(); + + let _ = funcall().err(); + + let _ = funcall().ok(); + + let _ = funcall().err(); + + #[allow(clippy::redundant_pattern)] + let _ = funcall().ok(); + + struct S; + + impl std::ops::Neg for S { + type Output = Result; + + fn neg(self) -> Self::Output { + funcall() + } + } + + // Suggestion should be properly parenthesized + let _ = (-S).ok(); + + no_lint(); +} + +fn no_lint() { + let _ = match funcall() { + Ok(v) if v > 3 => Some(v), + _ => None, + }; + + let _ = match funcall() { + Err(_) => None, + Ok(3) => None, + Ok(v) => Some(v), + }; + + let _ = match funcall() { + _ => None, + Ok(v) => Some(v), + }; + + let _ = match funcall() { + Err(_) | Ok(3) => None, + Ok(v) => Some(v), + }; + + #[expect(clippy::redundant_pattern)] + let _ = match funcall() { + _v @ _ => None, + Ok(v) => Some(v), + }; + + // Content of `Option` and matching content of `Result` do + // not have the same type. + let _: Option<&dyn std::any::Any> = match Ok::<_, ()>(&1) { + Ok(v) => Some(v), + _ => None, + }; + + let _ = match Ok::<_, ()>(&1) { + _x => None, + Ok(v) => Some(v), + }; + + let _ = match Ok::<_, std::convert::Infallible>(1) { + Ok(3) => None, + Ok(v) => Some(v), + }; +} + +const fn cf(x: Result) -> Option { + // Do not lint in const code + match x { + Ok(v) => Some(v), + Err(_) => None, + } +} diff --git a/tests/ui/manual_ok_err.rs b/tests/ui/manual_ok_err.rs new file mode 100644 index 000000000000..03ad773f47cf --- /dev/null +++ b/tests/ui/manual_ok_err.rs @@ -0,0 +1,125 @@ +#![warn(clippy::manual_ok_err)] + +fn funcall() -> Result { + todo!() +} + +fn main() { + let _ = match funcall() { + //~^ manual_ok_err + Ok(v) => Some(v), + Err(_) => None, + }; + + let _ = match funcall() { + //~^ manual_ok_err + Ok(v) => Some(v), + _v => None, + }; + + let _ = match funcall() { + //~^ manual_ok_err + Err(v) => Some(v), + Ok(_) => None, + }; + + let _ = match funcall() { + //~^ manual_ok_err + Err(v) => Some(v), + _v => None, + }; + + let _ = if let Ok(v) = funcall() { + //~^ manual_ok_err + Some(v) + } else { + None + }; + + let _ = if let Err(v) = funcall() { + //~^ manual_ok_err + Some(v) + } else { + None + }; + + #[allow(clippy::redundant_pattern)] + let _ = match funcall() { + //~^ manual_ok_err + Ok(v) => Some(v), + _v @ _ => None, + }; + + struct S; + + impl std::ops::Neg for S { + type Output = Result; + + fn neg(self) -> Self::Output { + funcall() + } + } + + // Suggestion should be properly parenthesized + let _ = match -S { + //~^ manual_ok_err + Ok(v) => Some(v), + _ => None, + }; + + no_lint(); +} + +fn no_lint() { + let _ = match funcall() { + Ok(v) if v > 3 => Some(v), + _ => None, + }; + + let _ = match funcall() { + Err(_) => None, + Ok(3) => None, + Ok(v) => Some(v), + }; + + let _ = match funcall() { + _ => None, + Ok(v) => Some(v), + }; + + let _ = match funcall() { + Err(_) | Ok(3) => None, + Ok(v) => Some(v), + }; + + #[expect(clippy::redundant_pattern)] + let _ = match funcall() { + _v @ _ => None, + Ok(v) => Some(v), + }; + + // Content of `Option` and matching content of `Result` do + // not have the same type. + let _: Option<&dyn std::any::Any> = match Ok::<_, ()>(&1) { + Ok(v) => Some(v), + _ => None, + }; + + let _ = match Ok::<_, ()>(&1) { + _x => None, + Ok(v) => Some(v), + }; + + let _ = match Ok::<_, std::convert::Infallible>(1) { + Ok(3) => None, + Ok(v) => Some(v), + }; +} + +const fn cf(x: Result) -> Option { + // Do not lint in const code + match x { + Ok(v) => Some(v), + Err(_) => None, + } +} diff --git a/tests/ui/manual_ok_err.stderr b/tests/ui/manual_ok_err.stderr new file mode 100644 index 000000000000..d0d5e2c81e96 --- /dev/null +++ b/tests/ui/manual_ok_err.stderr @@ -0,0 +1,95 @@ +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:8:13 + | +LL | let _ = match funcall() { + | _____________^ +LL | | +LL | | Ok(v) => Some(v), +LL | | Err(_) => None, +LL | | }; + | |_____^ help: replace with: `funcall().ok()` + | + = note: `-D clippy::manual-ok-err` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_ok_err)]` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:14:13 + | +LL | let _ = match funcall() { + | _____________^ +LL | | +LL | | Ok(v) => Some(v), +LL | | _v => None, +LL | | }; + | |_____^ help: replace with: `funcall().ok()` + +error: manual implementation of `err` + --> tests/ui/manual_ok_err.rs:20:13 + | +LL | let _ = match funcall() { + | _____________^ +LL | | +LL | | Err(v) => Some(v), +LL | | Ok(_) => None, +LL | | }; + | |_____^ help: replace with: `funcall().err()` + +error: manual implementation of `err` + --> tests/ui/manual_ok_err.rs:26:13 + | +LL | let _ = match funcall() { + | _____________^ +LL | | +LL | | Err(v) => Some(v), +LL | | _v => None, +LL | | }; + | |_____^ help: replace with: `funcall().err()` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:32:13 + | +LL | let _ = if let Ok(v) = funcall() { + | _____________^ +LL | | +LL | | Some(v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ help: replace with: `funcall().ok()` + +error: manual implementation of `err` + --> tests/ui/manual_ok_err.rs:39:13 + | +LL | let _ = if let Err(v) = funcall() { + | _____________^ +LL | | +LL | | Some(v) +LL | | } else { +LL | | None +LL | | }; + | |_____^ help: replace with: `funcall().err()` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:47:13 + | +LL | let _ = match funcall() { + | _____________^ +LL | | +LL | | Ok(v) => Some(v), +LL | | _v @ _ => None, +LL | | }; + | |_____^ help: replace with: `funcall().ok()` + +error: manual implementation of `ok` + --> tests/ui/manual_ok_err.rs:64:13 + | +LL | let _ = match -S { + | _____________^ +LL | | +LL | | Ok(v) => Some(v), +LL | | _ => None, +LL | | }; + | |_____^ help: replace with: `(-S).ok()` + +error: aborting due to 8 previous errors + diff --git a/tests/ui/manual_repeat_n.fixed b/tests/ui/manual_repeat_n.fixed new file mode 100644 index 000000000000..4235b02a89e3 --- /dev/null +++ b/tests/ui/manual_repeat_n.fixed @@ -0,0 +1,30 @@ +#![warn(clippy::manual_repeat_n)] + +use std::iter::repeat; + +fn main() { + let _ = std::iter::repeat_n(10, 3); + + let _ = std::iter::repeat_n(String::from("foo"), 4); + + for value in std::iter::repeat_n(5, 3) {} + + let _: Vec<_> = std::iter::repeat_n(String::from("bar"), 10).collect(); + + let _ = std::iter::repeat_n(vec![1, 2], 2); +} + +mod foo_lib { + pub fn iter() -> std::iter::Take> { + todo!() + } +} + +fn foo() { + let _ = match 1 { + 1 => foo_lib::iter(), + // Shouldn't lint because `external_lib::iter` doesn't return `std::iter::RepeatN`. + 2 => std::iter::repeat([1, 2].as_slice()).take(2), + _ => todo!(), + }; +} diff --git a/tests/ui/manual_repeat_n.rs b/tests/ui/manual_repeat_n.rs new file mode 100644 index 000000000000..dbf9ac6a14af --- /dev/null +++ b/tests/ui/manual_repeat_n.rs @@ -0,0 +1,30 @@ +#![warn(clippy::manual_repeat_n)] + +use std::iter::repeat; + +fn main() { + let _ = repeat(10).take(3); + + let _ = repeat(String::from("foo")).take(4); + + for value in std::iter::repeat(5).take(3) {} + + let _: Vec<_> = std::iter::repeat(String::from("bar")).take(10).collect(); + + let _ = repeat(vec![1, 2]).take(2); +} + +mod foo_lib { + pub fn iter() -> std::iter::Take> { + todo!() + } +} + +fn foo() { + let _ = match 1 { + 1 => foo_lib::iter(), + // Shouldn't lint because `external_lib::iter` doesn't return `std::iter::RepeatN`. + 2 => std::iter::repeat([1, 2].as_slice()).take(2), + _ => todo!(), + }; +} diff --git a/tests/ui/manual_repeat_n.stderr b/tests/ui/manual_repeat_n.stderr new file mode 100644 index 000000000000..87395b3f8bf1 --- /dev/null +++ b/tests/ui/manual_repeat_n.stderr @@ -0,0 +1,35 @@ +error: this `repeat().take()` can be written more concisely + --> tests/ui/manual_repeat_n.rs:6:13 + | +LL | let _ = repeat(10).take(3); + | ^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(10, 3)` + | + = note: `-D clippy::manual-repeat-n` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::manual_repeat_n)]` + +error: this `repeat().take()` can be written more concisely + --> tests/ui/manual_repeat_n.rs:8:13 + | +LL | let _ = repeat(String::from("foo")).take(4); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(String::from("foo"), 4)` + +error: this `repeat().take()` can be written more concisely + --> tests/ui/manual_repeat_n.rs:10:18 + | +LL | for value in std::iter::repeat(5).take(3) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(5, 3)` + +error: this `repeat().take()` can be written more concisely + --> tests/ui/manual_repeat_n.rs:12:21 + | +LL | let _: Vec<_> = std::iter::repeat(String::from("bar")).take(10).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(String::from("bar"), 10)` + +error: this `repeat().take()` can be written more concisely + --> tests/ui/manual_repeat_n.rs:14:13 + | +LL | let _ = repeat(vec![1, 2]).take(2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using `repeat_n()` instead: `std::iter::repeat_n(vec![1, 2], 2)` + +error: aborting due to 5 previous errors + diff --git a/tests/ui/manual_str_repeat.fixed b/tests/ui/manual_str_repeat.fixed index 5f2f1bd9916d..da6f36f53b0d 100644 --- a/tests/ui/manual_str_repeat.fixed +++ b/tests/ui/manual_str_repeat.fixed @@ -1,4 +1,4 @@ -#![allow(non_local_definitions)] +#![allow(non_local_definitions, clippy::manual_repeat_n)] #![warn(clippy::manual_str_repeat)] use std::borrow::Cow; diff --git a/tests/ui/manual_str_repeat.rs b/tests/ui/manual_str_repeat.rs index 3e3c7f4db4a2..686ed4fee7d1 100644 --- a/tests/ui/manual_str_repeat.rs +++ b/tests/ui/manual_str_repeat.rs @@ -1,4 +1,4 @@ -#![allow(non_local_definitions)] +#![allow(non_local_definitions, clippy::manual_repeat_n)] #![warn(clippy::manual_str_repeat)] use std::borrow::Cow; diff --git a/tests/ui/map_with_unused_argument_over_ranges.fixed b/tests/ui/map_with_unused_argument_over_ranges.fixed index cf520e71a64f..18716e93d1e9 100644 --- a/tests/ui/map_with_unused_argument_over_ranges.fixed +++ b/tests/ui/map_with_unused_argument_over_ranges.fixed @@ -14,7 +14,7 @@ fn do_something_interesting(x: usize, y: usize) -> usize { todo!() } -macro_rules! gen { +macro_rules! r#gen { () => { (0..10).map(|_| do_something()); }; @@ -45,7 +45,7 @@ fn main() { std::iter::repeat_with(|| do_something()).take(1); std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); // These should not be raised - gen!(); + r#gen!(); let lower = 2; let lower_fn = || 2; (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled diff --git a/tests/ui/map_with_unused_argument_over_ranges.rs b/tests/ui/map_with_unused_argument_over_ranges.rs index 298eee9ca3fa..596afd51e61f 100644 --- a/tests/ui/map_with_unused_argument_over_ranges.rs +++ b/tests/ui/map_with_unused_argument_over_ranges.rs @@ -14,7 +14,7 @@ fn do_something_interesting(x: usize, y: usize) -> usize { todo!() } -macro_rules! gen { +macro_rules! r#gen { () => { (0..10).map(|_| do_something()); }; @@ -45,7 +45,7 @@ fn main() { (9..=9).map(|_| do_something()); (1..=1 << 4).map(|_| do_something()); // These should not be raised - gen!(); + r#gen!(); let lower = 2; let lower_fn = || 2; (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled diff --git a/tests/ui/map_with_unused_argument_over_ranges_nostd.fixed b/tests/ui/map_with_unused_argument_over_ranges_nostd.fixed new file mode 100644 index 000000000000..65e59774905c --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges_nostd.fixed @@ -0,0 +1,8 @@ +#![warn(clippy::map_with_unused_argument_over_ranges)] +#![no_std] +extern crate alloc; +use alloc::vec::Vec; + +fn nostd(v: &mut [i32]) { + let _: Vec<_> = core::iter::repeat_n(3 + 1, 10).collect(); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges_nostd.rs b/tests/ui/map_with_unused_argument_over_ranges_nostd.rs new file mode 100644 index 000000000000..dda7a69b33f9 --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges_nostd.rs @@ -0,0 +1,8 @@ +#![warn(clippy::map_with_unused_argument_over_ranges)] +#![no_std] +extern crate alloc; +use alloc::vec::Vec; + +fn nostd(v: &mut [i32]) { + let _: Vec<_> = (0..10).map(|_| 3 + 1).collect(); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges_nostd.stderr b/tests/ui/map_with_unused_argument_over_ranges_nostd.stderr new file mode 100644 index 000000000000..d47f3d09175b --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges_nostd.stderr @@ -0,0 +1,15 @@ +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges_nostd.rs:7:21 + | +LL | let _: Vec<_> = (0..10).map(|_| 3 + 1).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-with-unused-argument-over-ranges` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_with_unused_argument_over_ranges)]` +help: remove the explicit range and use `repeat_n` + | +LL | let _: Vec<_> = core::iter::repeat_n(3 + 1, 10).collect(); + | ~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~ + +error: aborting due to 1 previous error + diff --git a/tests/ui/match_bool.fixed b/tests/ui/match_bool.fixed new file mode 100644 index 000000000000..61a8e54fa10c --- /dev/null +++ b/tests/ui/match_bool.fixed @@ -0,0 +1,58 @@ +#![deny(clippy::match_bool)] +#![allow(clippy::nonminimal_bool, clippy::eq_op)] + +fn match_bool() { + let test: bool = true; + + if test { 0 } else { 42 }; + + let option = 1; + if option == 1 { 1 } else { 0 }; + + if !test { + println!("Noooo!"); + }; + + if !test { + println!("Noooo!"); + }; + + if !(test && test) { + println!("Noooo!"); + }; + + if !test { + println!("Noooo!"); + } else { + println!("Yes!"); + }; + + // Not linted + match option { + 1..=10 => 1, + 11..=20 => 2, + _ => 3, + }; + + // Don't lint + let _ = match test { + #[cfg(feature = "foo")] + true if option == 5 => 10, + true => 0, + false => 1, + }; + + let _ = if test && option == 5 { 10 } else { 1 }; + + let _ = if !test && option == 5 { 10 } else { 1 }; + + if test && option == 5 { println!("Hello") }; + + if !(test && option == 5) { println!("Hello") }; + + if !test && option == 5 { println!("Hello") }; + + if !(!test && option == 5) { println!("Hello") }; +} + +fn main() {} diff --git a/tests/ui/match_bool.rs b/tests/ui/match_bool.rs index f84af393e47f..9c81d2917869 100644 --- a/tests/ui/match_bool.rs +++ b/tests/ui/match_bool.rs @@ -1,24 +1,24 @@ -//@no-rustfix: overlapping suggestions #![deny(clippy::match_bool)] +#![allow(clippy::nonminimal_bool, clippy::eq_op)] fn match_bool() { let test: bool = true; match test { - //~^ ERROR: you seem to be trying to match on a boolean expression + //~^ ERROR: `match` on a boolean expression true => 0, false => 42, }; let option = 1; match option == 1 { - //~^ ERROR: you seem to be trying to match on a boolean expression + //~^ ERROR: `match` on a boolean expression true => 1, false => 0, }; match test { - //~^ ERROR: you seem to be trying to match on a boolean expression + //~^ ERROR: `match` on a boolean expression true => (), false => { println!("Noooo!"); @@ -26,7 +26,7 @@ fn match_bool() { }; match test { - //~^ ERROR: you seem to be trying to match on a boolean expression + //~^ ERROR: `match` on a boolean expression false => { println!("Noooo!"); }, @@ -34,11 +34,7 @@ fn match_bool() { }; match test && test { - //~^ ERROR: this boolean expression can be simplified - //~| NOTE: `-D clippy::nonminimal-bool` implied by `-D warnings` - //~| ERROR: you seem to be trying to match on a boolean expression - //~| ERROR: equal expressions as operands to `&&` - //~| NOTE: `#[deny(clippy::eq_op)]` on by default + //~^ ERROR: `match` on a boolean expression false => { println!("Noooo!"); }, @@ -46,7 +42,7 @@ fn match_bool() { }; match test { - //~^ ERROR: you seem to be trying to match on a boolean expression + //~^ ERROR: `match` on a boolean expression false => { println!("Noooo!"); }, @@ -69,6 +65,42 @@ fn match_bool() { true => 0, false => 1, }; + + let _ = match test { + //~^ ERROR: `match` on a boolean expression + true if option == 5 => 10, + _ => 1, + }; + + let _ = match test { + //~^ ERROR: `match` on a boolean expression + false if option == 5 => 10, + _ => 1, + }; + + match test { + //~^ ERROR: `match` on a boolean expression + true if option == 5 => println!("Hello"), + _ => (), + }; + + match test { + //~^ ERROR: `match` on a boolean expression + true if option == 5 => (), + _ => println!("Hello"), + }; + + match test { + //~^ ERROR: `match` on a boolean expression + false if option == 5 => println!("Hello"), + _ => (), + }; + + match test { + //~^ ERROR: `match` on a boolean expression + false if option == 5 => (), + _ => println!("Hello"), + }; } fn main() {} diff --git a/tests/ui/match_bool.stderr b/tests/ui/match_bool.stderr index fb24e67eceef..a4e504a0a82e 100644 --- a/tests/ui/match_bool.stderr +++ b/tests/ui/match_bool.stderr @@ -1,13 +1,4 @@ -error: this boolean expression can be simplified - --> tests/ui/match_bool.rs:36:11 - | -LL | match test && test { - | ^^^^^^^^^^^^ help: try: `test` - | - = note: `-D clippy::nonminimal-bool` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::nonminimal_bool)]` - -error: you seem to be trying to match on a boolean expression +error: `match` on a boolean expression --> tests/ui/match_bool.rs:7:5 | LL | / match test { @@ -18,12 +9,12 @@ LL | | }; | |_____^ help: consider using an `if`/`else` expression: `if test { 0 } else { 42 }` | note: the lint level is defined here - --> tests/ui/match_bool.rs:2:9 + --> tests/ui/match_bool.rs:1:9 | LL | #![deny(clippy::match_bool)] | ^^^^^^^^^^^^^^^^^^ -error: you seem to be trying to match on a boolean expression +error: `match` on a boolean expression --> tests/ui/match_bool.rs:14:5 | LL | / match option == 1 { @@ -33,7 +24,7 @@ LL | | false => 0, LL | | }; | |_____^ help: consider using an `if`/`else` expression: `if option == 1 { 1 } else { 0 }` -error: you seem to be trying to match on a boolean expression +error: `match` on a boolean expression --> tests/ui/match_bool.rs:20:5 | LL | / match test { @@ -52,7 +43,7 @@ LL + println!("Noooo!"); LL ~ }; | -error: you seem to be trying to match on a boolean expression +error: `match` on a boolean expression --> tests/ui/match_bool.rs:28:5 | LL | / match test { @@ -71,11 +62,14 @@ LL + println!("Noooo!"); LL ~ }; | -error: you seem to be trying to match on a boolean expression +error: `match` on a boolean expression --> tests/ui/match_bool.rs:36:5 | LL | / match test && test { -... | +LL | | +LL | | false => { +LL | | println!("Noooo!"); +LL | | }, LL | | _ => (), LL | | }; | |_____^ @@ -87,16 +81,8 @@ LL + println!("Noooo!"); LL ~ }; | -error: equal expressions as operands to `&&` - --> tests/ui/match_bool.rs:36:11 - | -LL | match test && test { - | ^^^^^^^^^^^^ - | - = note: `#[deny(clippy::eq_op)]` on by default - -error: you seem to be trying to match on a boolean expression - --> tests/ui/match_bool.rs:48:5 +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:44:5 | LL | / match test { LL | | @@ -109,12 +95,74 @@ LL | | }; | help: consider using an `if`/`else` expression | -LL ~ if test { -LL + println!("Yes!"); -LL + } else { +LL ~ if !test { LL + println!("Noooo!"); +LL + } else { +LL + println!("Yes!"); LL ~ }; | -error: aborting due to 8 previous errors +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:69:13 + | +LL | let _ = match test { + | _____________^ +LL | | +LL | | true if option == 5 => 10, +LL | | _ => 1, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if test && option == 5 { 10 } else { 1 }` + +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:75:13 + | +LL | let _ = match test { + | _____________^ +LL | | +LL | | false if option == 5 => 10, +LL | | _ => 1, +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if !test && option == 5 { 10 } else { 1 }` + +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:81:5 + | +LL | / match test { +LL | | +LL | | true if option == 5 => println!("Hello"), +LL | | _ => (), +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if test && option == 5 { println!("Hello") }` + +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:87:5 + | +LL | / match test { +LL | | +LL | | true if option == 5 => (), +LL | | _ => println!("Hello"), +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if !(test && option == 5) { println!("Hello") }` + +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:93:5 + | +LL | / match test { +LL | | +LL | | false if option == 5 => println!("Hello"), +LL | | _ => (), +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if !test && option == 5 { println!("Hello") }` + +error: `match` on a boolean expression + --> tests/ui/match_bool.rs:99:5 + | +LL | / match test { +LL | | +LL | | false if option == 5 => (), +LL | | _ => println!("Hello"), +LL | | }; + | |_____^ help: consider using an `if`/`else` expression: `if !(!test && option == 5) { println!("Hello") }` + +error: aborting due to 12 previous errors 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 fdde68790a8c..cdfdcd5007a8 100644 --- a/tests/ui/missing_const_for_fn/cant_be_const.rs +++ b/tests/ui/missing_const_for_fn/cant_be_const.rs @@ -210,3 +210,9 @@ mod with_ty_alias { let _: Foo = 1; } } + +// Do not lint because mutable references in const functions are unstable in 1.82 +#[clippy::msrv = "1.82"] +fn mut_add(x: &mut i32) { + *x += 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 dd9dedcdd044..689060468c57 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -213,3 +213,8 @@ mod extern_fn { const extern "system-unwind" fn system_unwind() {} //~^ ERROR: this could be a `const fn` } + +const fn mut_add(x: &mut i32) { + //~^ ERROR: this could be a `const fn` + *x += 1; +} 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 f974478540cd..492c47d7e49b 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -213,3 +213,8 @@ mod extern_fn { extern "system-unwind" fn system_unwind() {} //~^ ERROR: this could be a `const fn` } + +fn mut_add(x: &mut i32) { + //~^ ERROR: this could be a `const fn` + *x += 1; +} 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 33836bdfe9f8..a06703e2ebf2 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -316,5 +316,19 @@ help: make the function `const` LL | const extern "system-unwind" fn system_unwind() {} | +++++ -error: aborting due to 24 previous errors +error: this could be a `const fn` + --> tests/ui/missing_const_for_fn/could_be_const.rs:217:1 + | +LL | / fn mut_add(x: &mut i32) { +LL | | +LL | | *x += 1; +LL | | } + | |_^ + | +help: make the function `const` + | +LL | const fn mut_add(x: &mut i32) { + | +++++ + +error: aborting due to 25 previous errors diff --git a/tests/ui/needless_as_bytes.fixed b/tests/ui/needless_as_bytes.fixed index 042342311fdf..74b4ba5be798 100644 --- a/tests/ui/needless_as_bytes.fixed +++ b/tests/ui/needless_as_bytes.fixed @@ -1,5 +1,6 @@ #![warn(clippy::needless_as_bytes)] #![allow(clippy::const_is_empty)] +#![feature(exact_size_is_empty)] struct S; @@ -7,6 +8,9 @@ impl S { fn as_bytes(&self) -> &[u8] { &[] } + fn bytes(&self) -> &[u8] { + &[] + } } fn main() { @@ -15,6 +19,11 @@ fn main() { println!("len = {}", "some string".len()); //~^ needless_as_bytes } + if "some string".is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".len()); + //~^ needless_as_bytes + } let s = String::from("yet another string"); if s.is_empty() { @@ -22,6 +31,11 @@ fn main() { println!("len = {}", s.len()); //~^ needless_as_bytes } + if s.is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.len()); + //~^ needless_as_bytes + } // Do not lint let _ = S.as_bytes().is_empty(); @@ -36,6 +50,18 @@ fn main() { }; } m!(1).as_bytes().len(); + let _ = S.bytes().is_empty(); + let _ = S.bytes().len(); + let _ = (&String::new() as &dyn Bytes).bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".bytes() + }; + } + m!(1).bytes().len(); m!(2).len(); } @@ -48,3 +74,13 @@ impl AsBytes for String { &[] } } + +pub trait Bytes { + fn bytes(&self) -> &[u8]; +} + +impl Bytes for String { + fn bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.rs b/tests/ui/needless_as_bytes.rs index c481e041e0ab..ffcce60bbbef 100644 --- a/tests/ui/needless_as_bytes.rs +++ b/tests/ui/needless_as_bytes.rs @@ -1,5 +1,6 @@ #![warn(clippy::needless_as_bytes)] #![allow(clippy::const_is_empty)] +#![feature(exact_size_is_empty)] struct S; @@ -7,6 +8,9 @@ impl S { fn as_bytes(&self) -> &[u8] { &[] } + fn bytes(&self) -> &[u8] { + &[] + } } fn main() { @@ -15,6 +19,11 @@ fn main() { println!("len = {}", "some string".as_bytes().len()); //~^ needless_as_bytes } + if "some string".bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".bytes().len()); + //~^ needless_as_bytes + } let s = String::from("yet another string"); if s.as_bytes().is_empty() { @@ -22,6 +31,11 @@ fn main() { println!("len = {}", s.as_bytes().len()); //~^ needless_as_bytes } + if s.bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.bytes().len()); + //~^ needless_as_bytes + } // Do not lint let _ = S.as_bytes().is_empty(); @@ -36,6 +50,18 @@ fn main() { }; } m!(1).as_bytes().len(); + let _ = S.bytes().is_empty(); + let _ = S.bytes().len(); + let _ = (&String::new() as &dyn Bytes).bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".bytes() + }; + } + m!(1).bytes().len(); m!(2).len(); } @@ -48,3 +74,13 @@ impl AsBytes for String { &[] } } + +pub trait Bytes { + fn bytes(&self) -> &[u8]; +} + +impl Bytes for String { + fn bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.stderr b/tests/ui/needless_as_bytes.stderr index 3391238a142b..138c6630ae7d 100644 --- a/tests/ui/needless_as_bytes.stderr +++ b/tests/ui/needless_as_bytes.stderr @@ -1,5 +1,5 @@ -error: needless call to `as_bytes()` - --> tests/ui/needless_as_bytes.rs:13:8 +error: needless call to `as_bytes` + --> tests/ui/needless_as_bytes.rs:17:8 | LL | if "some string".as_bytes().is_empty() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `"some string".is_empty()` @@ -7,23 +7,47 @@ LL | if "some string".as_bytes().is_empty() { = note: `-D clippy::needless-as-bytes` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_as_bytes)]` -error: needless call to `as_bytes()` - --> tests/ui/needless_as_bytes.rs:15:30 +error: needless call to `as_bytes` + --> tests/ui/needless_as_bytes.rs:19:30 | LL | println!("len = {}", "some string".as_bytes().len()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `"some string".len()` -error: needless call to `as_bytes()` - --> tests/ui/needless_as_bytes.rs:20:8 +error: needless call to `bytes` + --> tests/ui/needless_as_bytes.rs:22:8 + | +LL | if "some string".bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `"some string".is_empty()` + +error: needless call to `bytes` + --> tests/ui/needless_as_bytes.rs:24:30 + | +LL | println!("len = {}", "some string".bytes().len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `"some string".len()` + +error: needless call to `as_bytes` + --> tests/ui/needless_as_bytes.rs:29:8 | LL | if s.as_bytes().is_empty() { | ^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `s.is_empty()` -error: needless call to `as_bytes()` - --> tests/ui/needless_as_bytes.rs:22:30 +error: needless call to `as_bytes` + --> tests/ui/needless_as_bytes.rs:31:30 | LL | println!("len = {}", s.as_bytes().len()); | ^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `s.len()` -error: aborting due to 4 previous errors +error: needless call to `bytes` + --> tests/ui/needless_as_bytes.rs:34:8 + | +LL | if s.bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `s.is_empty()` + +error: needless call to `bytes` + --> tests/ui/needless_as_bytes.rs:36:30 + | +LL | println!("len = {}", s.bytes().len()); + | ^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `s.len()` + +error: aborting due to 8 previous errors diff --git a/tests/ui/needless_late_init.fixed b/tests/ui/needless_late_init.fixed index 6db870490445..b4bd53ce7bf7 100644 --- a/tests/ui/needless_late_init.fixed +++ b/tests/ui/needless_late_init.fixed @@ -270,3 +270,14 @@ fn issue8911() -> u32 { 3 } + +macro_rules! issue13776_mac { + ($var:expr, $val:literal) => { + $var = $val; + }; +} + +fn issue13776() { + let x; + issue13776_mac!(x, 10); // should not lint +} diff --git a/tests/ui/needless_late_init.rs b/tests/ui/needless_late_init.rs index c1e86212a08b..e25483625a68 100644 --- a/tests/ui/needless_late_init.rs +++ b/tests/ui/needless_late_init.rs @@ -270,3 +270,14 @@ fn issue8911() -> u32 { 3 } + +macro_rules! issue13776_mac { + ($var:expr, $val:literal) => { + $var = $val; + }; +} + +fn issue13776() { + let x; + issue13776_mac!(x, 10); // should not lint +} diff --git a/tests/ui/needless_lifetimes.fixed b/tests/ui/needless_lifetimes.fixed index 8196d608abd2..86cf9a9cdb63 100644 --- a/tests/ui/needless_lifetimes.fixed +++ b/tests/ui/needless_lifetimes.fixed @@ -6,6 +6,7 @@ clippy::boxed_local, clippy::extra_unused_type_parameters, clippy::needless_pass_by_value, + clippy::redundant_allocation, clippy::unnecessary_wraps, dyn_drop, clippy::get_first @@ -443,11 +444,20 @@ mod issue7296 { fn implicit_mut(&mut self) -> &() { &() } - - fn explicit<'a>(self: &'a Arc) -> &'a () { + #[clippy::msrv = "1.81"] + fn explicit(self: &Arc) -> &() { &() } - fn explicit_mut<'a>(self: &'a mut Rc) -> &'a () { + #[clippy::msrv = "1.81"] + fn explicit_mut(self: &mut Rc) -> &() { + &() + } + #[clippy::msrv = "1.80"] + fn explicit_older<'a>(self: &'a Arc) -> &'a () { + &() + } + #[clippy::msrv = "1.80"] + fn explicit_mut_older<'a>(self: &'a mut Rc) -> &'a () { &() } @@ -462,8 +472,16 @@ mod issue7296 { &() } - fn explicit<'a>(self: &'a Arc) -> &'a (); - fn explicit_provided<'a>(self: &'a Arc) -> &'a () { + #[clippy::msrv = "1.81"] + fn explicit(self: &Arc) -> &(); + #[clippy::msrv = "1.81"] + fn explicit_provided(self: &Arc) -> &() { + &() + } + #[clippy::msrv = "1.80"] + fn explicit_older<'a>(self: &'a Arc) -> &'a (); + #[clippy::msrv = "1.80"] + fn explicit_provided_older<'a>(self: &'a Arc) -> &'a () { &() } @@ -576,4 +594,85 @@ mod issue13749bis { impl<'a, T: 'a> Generic {} } +mod issue13923 { + struct Py<'py> { + data: &'py str, + } + + enum Content<'t, 'py> { + Py(Py<'py>), + T1(&'t str), + T2(&'t str), + } + + enum ContentString<'t> { + T1(&'t str), + T2(&'t str), + } + + impl<'t, 'py> ContentString<'t> { + // `'py` cannot be elided + fn map_content1(self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t> ContentString<'t> { + // `'py` can be elided because of `&self` + fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t> ContentString<'t> { + // `'py` can be elided because of `&'_ self` + fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t, 'py> ContentString<'t> { + // `'py` should not be elided as the default lifetime, even if working, could be named as `'t` + fn map_content4(self, f: impl FnOnce(&'t str) -> &'t str, o: &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(_) => Content::T2(o), + } + } + } + + impl<'t> ContentString<'t> { + // `'py` can be elided because of `&Self` + fn map_content5( + self: std::pin::Pin<&Self>, + f: impl FnOnce(&'t str) -> &'t str, + o: &'t str, + ) -> Content<'t, '_> { + match *self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(_) => Content::T2(o), + } + } + } + + struct Cx<'a, 'b> { + a: &'a u32, + b: &'b u32, + } + + // `'c` cannot be elided because we have several input lifetimes + fn one_explicit<'b>(x: Cx<'_, 'b>) -> &'b u32 { + x.b + } +} + fn main() {} diff --git a/tests/ui/needless_lifetimes.rs b/tests/ui/needless_lifetimes.rs index b55dd99c46d0..1ee0f4c6092a 100644 --- a/tests/ui/needless_lifetimes.rs +++ b/tests/ui/needless_lifetimes.rs @@ -6,6 +6,7 @@ clippy::boxed_local, clippy::extra_unused_type_parameters, clippy::needless_pass_by_value, + clippy::redundant_allocation, clippy::unnecessary_wraps, dyn_drop, clippy::get_first @@ -443,13 +444,22 @@ mod issue7296 { fn implicit_mut<'a>(&'a mut self) -> &'a () { &() } - + #[clippy::msrv = "1.81"] fn explicit<'a>(self: &'a Arc) -> &'a () { &() } + #[clippy::msrv = "1.81"] fn explicit_mut<'a>(self: &'a mut Rc) -> &'a () { &() } + #[clippy::msrv = "1.80"] + fn explicit_older<'a>(self: &'a Arc) -> &'a () { + &() + } + #[clippy::msrv = "1.80"] + fn explicit_mut_older<'a>(self: &'a mut Rc) -> &'a () { + &() + } fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a () { &() @@ -462,10 +472,18 @@ mod issue7296 { &() } + #[clippy::msrv = "1.81"] fn explicit<'a>(self: &'a Arc) -> &'a (); + #[clippy::msrv = "1.81"] fn explicit_provided<'a>(self: &'a Arc) -> &'a () { &() } + #[clippy::msrv = "1.80"] + fn explicit_older<'a>(self: &'a Arc) -> &'a (); + #[clippy::msrv = "1.80"] + fn explicit_provided_older<'a>(self: &'a Arc) -> &'a () { + &() + } fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a (); fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { @@ -576,4 +594,85 @@ mod issue13749bis { impl<'a, T: 'a> Generic {} } +mod issue13923 { + struct Py<'py> { + data: &'py str, + } + + enum Content<'t, 'py> { + Py(Py<'py>), + T1(&'t str), + T2(&'t str), + } + + enum ContentString<'t> { + T1(&'t str), + T2(&'t str), + } + + impl<'t, 'py> ContentString<'t> { + // `'py` cannot be elided + fn map_content1(self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t, 'py> ContentString<'t> { + // `'py` can be elided because of `&self` + fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t, 'py> ContentString<'t> { + // `'py` can be elided because of `&'_ self` + fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(content) => Content::T2(f(content)), + } + } + } + + impl<'t, 'py> ContentString<'t> { + // `'py` should not be elided as the default lifetime, even if working, could be named as `'t` + fn map_content4(self, f: impl FnOnce(&'t str) -> &'t str, o: &'t str) -> Content<'t, 'py> { + match self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(_) => Content::T2(o), + } + } + } + + impl<'t, 'py> ContentString<'t> { + // `'py` can be elided because of `&Self` + fn map_content5( + self: std::pin::Pin<&Self>, + f: impl FnOnce(&'t str) -> &'t str, + o: &'t str, + ) -> Content<'t, 'py> { + match *self { + Self::T1(content) => Content::T1(f(content)), + Self::T2(_) => Content::T2(o), + } + } + } + + struct Cx<'a, 'b> { + a: &'a u32, + b: &'b u32, + } + + // `'c` cannot be elided because we have several input lifetimes + fn one_explicit<'b>(x: Cx<'_, 'b>) -> &'b u32 { + &x.b + } +} + fn main() {} diff --git a/tests/ui/needless_lifetimes.stderr b/tests/ui/needless_lifetimes.stderr index e56c914cc86d..465d529bf16c 100644 --- a/tests/ui/needless_lifetimes.stderr +++ b/tests/ui/needless_lifetimes.stderr @@ -1,5 +1,5 @@ error: elided lifetime has a name - --> tests/ui/needless_lifetimes.rs:266:52 + --> tests/ui/needless_lifetimes.rs:267:52 | LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { | -- ^ this elided lifetime gets resolved as `'a` @@ -10,7 +10,7 @@ LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { = help: to override `-D warnings` add `#[allow(elided_named_lifetimes)]` error: the following explicit lifetimes could be elided: 'a, 'b - --> tests/ui/needless_lifetimes.rs:17:23 + --> tests/ui/needless_lifetimes.rs:18:23 | LL | fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) {} | ^^ ^^ ^^ ^^ @@ -24,7 +24,7 @@ LL + fn distinct_lifetimes(_x: &u8, _y: &u8, _z: u8) {} | error: the following explicit lifetimes could be elided: 'a, 'b - --> tests/ui/needless_lifetimes.rs:19:24 + --> tests/ui/needless_lifetimes.rs:20:24 | LL | fn distinct_and_static<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: &'static u8) {} | ^^ ^^ ^^ ^^ @@ -36,7 +36,7 @@ LL + fn distinct_and_static(_x: &u8, _y: &u8, _z: &'static u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:29:15 + --> tests/ui/needless_lifetimes.rs:30:15 | LL | fn in_and_out<'a>(x: &'a u8, _y: u8) -> &'a u8 { | ^^ ^^ ^^ @@ -48,7 +48,7 @@ LL + fn in_and_out(x: &u8, _y: u8) -> &u8 { | error: the following explicit lifetimes could be elided: 'b - --> tests/ui/needless_lifetimes.rs:41:31 + --> tests/ui/needless_lifetimes.rs:42:31 | LL | fn multiple_in_and_out_2a<'a, 'b>(x: &'a u8, _y: &'b u8) -> &'a u8 { | ^^ ^^ @@ -60,7 +60,7 @@ LL + fn multiple_in_and_out_2a<'a>(x: &'a u8, _y: &u8) -> &'a u8 { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:48:27 + --> tests/ui/needless_lifetimes.rs:49:27 | LL | fn multiple_in_and_out_2b<'a, 'b>(_x: &'a u8, y: &'b u8) -> &'b u8 { | ^^ ^^ @@ -72,7 +72,7 @@ LL + fn multiple_in_and_out_2b<'b>(_x: &u8, y: &'b u8) -> &'b u8 { | error: the following explicit lifetimes could be elided: 'b - --> tests/ui/needless_lifetimes.rs:65:26 + --> tests/ui/needless_lifetimes.rs:66:26 | LL | fn deep_reference_1a<'a, 'b>(x: &'a u8, _y: &'b u8) -> Result<&'a u8, ()> { | ^^ ^^ @@ -84,7 +84,7 @@ LL + fn deep_reference_1a<'a>(x: &'a u8, _y: &u8) -> Result<&'a u8, ()> { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:72:22 + --> tests/ui/needless_lifetimes.rs:73:22 | LL | fn deep_reference_1b<'a, 'b>(_x: &'a u8, y: &'b u8) -> Result<&'b u8, ()> { | ^^ ^^ @@ -96,7 +96,7 @@ LL + fn deep_reference_1b<'b>(_x: &u8, y: &'b u8) -> Result<&'b u8, ()> { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:81:21 + --> tests/ui/needless_lifetimes.rs:82:21 | LL | fn deep_reference_3<'a>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> { | ^^ ^^ ^^ @@ -108,7 +108,7 @@ LL + fn deep_reference_3(x: &u8, _y: u8) -> Result<&u8, ()> { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:86:28 + --> tests/ui/needless_lifetimes.rs:87:28 | LL | fn where_clause_without_lt<'a, T>(x: &'a u8, _y: u8) -> Result<&'a u8, ()> | ^^ ^^ ^^ @@ -120,7 +120,7 @@ LL + fn where_clause_without_lt(x: &u8, _y: u8) -> Result<&u8, ()> | error: the following explicit lifetimes could be elided: 'a, 'b - --> tests/ui/needless_lifetimes.rs:98:21 + --> tests/ui/needless_lifetimes.rs:99:21 | LL | fn lifetime_param_2<'a, 'b>(_x: Ref<'a>, _y: &'b u8) {} | ^^ ^^ ^^ ^^ @@ -132,7 +132,7 @@ LL + fn lifetime_param_2(_x: Ref<'_>, _y: &u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:122:15 + --> tests/ui/needless_lifetimes.rs:123:15 | LL | fn fn_bound_2<'a, F, I>(_m: Lt<'a, I>, _f: F) -> Lt<'a, I> | ^^ ^^ ^^ @@ -144,7 +144,7 @@ LL + fn fn_bound_2(_m: Lt<'_, I>, _f: F) -> Lt<'_, I> | error: the following explicit lifetimes could be elided: 's - --> tests/ui/needless_lifetimes.rs:152:21 + --> tests/ui/needless_lifetimes.rs:153:21 | LL | fn self_and_out<'s>(&'s self) -> &'s u8 { | ^^ ^^ ^^ @@ -156,7 +156,7 @@ LL + fn self_and_out(&self) -> &u8 { | error: the following explicit lifetimes could be elided: 't - --> tests/ui/needless_lifetimes.rs:159:30 + --> tests/ui/needless_lifetimes.rs:160:30 | LL | fn self_and_in_out_1<'s, 't>(&'s self, _x: &'t u8) -> &'s u8 { | ^^ ^^ @@ -168,7 +168,7 @@ LL + fn self_and_in_out_1<'s>(&'s self, _x: &u8) -> &'s u8 { | error: the following explicit lifetimes could be elided: 's - --> tests/ui/needless_lifetimes.rs:166:26 + --> tests/ui/needless_lifetimes.rs:167:26 | LL | fn self_and_in_out_2<'s, 't>(&'s self, x: &'t u8) -> &'t u8 { | ^^ ^^ @@ -180,7 +180,7 @@ LL + fn self_and_in_out_2<'t>(&self, x: &'t u8) -> &'t u8 { | error: the following explicit lifetimes could be elided: 's, 't - --> tests/ui/needless_lifetimes.rs:170:29 + --> tests/ui/needless_lifetimes.rs:171:29 | LL | fn distinct_self_and_in<'s, 't>(&'s self, _x: &'t u8) {} | ^^ ^^ ^^ ^^ @@ -192,7 +192,7 @@ LL + fn distinct_self_and_in(&self, _x: &u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:189:19 + --> tests/ui/needless_lifetimes.rs:190:19 | LL | fn struct_with_lt<'a>(_foo: Foo<'a>) -> &'a str { | ^^ ^^ ^^ @@ -204,7 +204,7 @@ LL + fn struct_with_lt(_foo: Foo<'_>) -> &str { | error: the following explicit lifetimes could be elided: 'b - --> tests/ui/needless_lifetimes.rs:207:25 + --> tests/ui/needless_lifetimes.rs:208:25 | LL | fn struct_with_lt4a<'a, 'b>(_foo: &'a Foo<'b>) -> &'a str { | ^^ ^^ @@ -216,7 +216,7 @@ LL + fn struct_with_lt4a<'a>(_foo: &'a Foo<'_>) -> &'a str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:215:21 + --> tests/ui/needless_lifetimes.rs:216:21 | LL | fn struct_with_lt4b<'a, 'b>(_foo: &'a Foo<'b>) -> &'b str { | ^^ ^^ @@ -228,7 +228,7 @@ LL + fn struct_with_lt4b<'b>(_foo: &Foo<'b>) -> &'b str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:230:22 + --> tests/ui/needless_lifetimes.rs:231:22 | LL | fn trait_obj_elided2<'a>(_arg: &'a dyn Drop) -> &'a str { | ^^ ^^ ^^ @@ -240,7 +240,7 @@ LL + fn trait_obj_elided2(_arg: &dyn Drop) -> &str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:236:18 + --> tests/ui/needless_lifetimes.rs:237:18 | LL | fn alias_with_lt<'a>(_foo: FooAlias<'a>) -> &'a str { | ^^ ^^ ^^ @@ -252,7 +252,7 @@ LL + fn alias_with_lt(_foo: FooAlias<'_>) -> &str { | error: the following explicit lifetimes could be elided: 'b - --> tests/ui/needless_lifetimes.rs:254:24 + --> tests/ui/needless_lifetimes.rs:255:24 | LL | fn alias_with_lt4a<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'a str { | ^^ ^^ @@ -264,7 +264,7 @@ LL + fn alias_with_lt4a<'a>(_foo: &'a FooAlias<'_>) -> &'a str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:262:20 + --> tests/ui/needless_lifetimes.rs:263:20 | LL | fn alias_with_lt4b<'a, 'b>(_foo: &'a FooAlias<'b>) -> &'b str { | ^^ ^^ @@ -276,7 +276,7 @@ LL + fn alias_with_lt4b<'b>(_foo: &FooAlias<'b>) -> &'b str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:266:30 + --> tests/ui/needless_lifetimes.rs:267:30 | LL | fn named_input_elided_output<'a>(_arg: &'a str) -> &str { | ^^ ^^ ^ @@ -288,7 +288,7 @@ LL + fn named_input_elided_output(_arg: &str) -> &str { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:274:19 + --> tests/ui/needless_lifetimes.rs:275:19 | LL | fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { | ^^ ^^ @@ -300,7 +300,7 @@ LL + fn trait_bound_ok>(_: &u8, _: T) { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:310:24 + --> tests/ui/needless_lifetimes.rs:311:24 | LL | fn out_return_type_lts<'a>(e: &'a str) -> Cow<'a> { | ^^ ^^ ^^ @@ -312,7 +312,7 @@ LL + fn out_return_type_lts(e: &str) -> Cow<'_> { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:317:24 + --> tests/ui/needless_lifetimes.rs:318:24 | LL | fn needless_lt<'a>(x: &'a u8) {} | ^^ ^^ @@ -324,7 +324,7 @@ LL + fn needless_lt(x: &u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:321:24 + --> tests/ui/needless_lifetimes.rs:322:24 | LL | fn needless_lt<'a>(_x: &'a u8) {} | ^^ ^^ @@ -336,7 +336,7 @@ LL + fn needless_lt(_x: &u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:332:10 + --> tests/ui/needless_lifetimes.rs:333:10 | LL | impl<'a> Foo for Baz<'a> {} | ^^ ^^ @@ -348,7 +348,7 @@ LL + impl Foo for Baz<'_> {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:334:16 + --> tests/ui/needless_lifetimes.rs:335:16 | LL | fn baz<'a>(&'a self) -> impl Foo + 'a { | ^^ ^^ ^^ @@ -360,7 +360,7 @@ LL + fn baz(&self) -> impl Foo + '_ { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:366:55 + --> tests/ui/needless_lifetimes.rs:367:55 | LL | fn impl_trait_elidable_nested_anonymous_lifetimes<'a>(i: &'a i32, f: impl Fn(&i32) -> &i32) -> &'a i32 { | ^^ ^^ ^^ @@ -372,7 +372,7 @@ LL + fn impl_trait_elidable_nested_anonymous_lifetimes(i: &i32, f: impl Fn(& | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:375:26 + --> tests/ui/needless_lifetimes.rs:376:26 | LL | fn generics_elidable<'a, T: Fn(&i32) -> &i32>(i: &'a i32, f: T) -> &'a i32 { | ^^ ^^ ^^ @@ -384,7 +384,7 @@ LL + fn generics_elidable &i32>(i: &i32, f: T) -> &i32 { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:387:30 + --> tests/ui/needless_lifetimes.rs:388:30 | LL | fn where_clause_elidable<'a, T>(i: &'a i32, f: T) -> &'a i32 | ^^ ^^ ^^ @@ -396,7 +396,7 @@ LL + fn where_clause_elidable(i: &i32, f: T) -> &i32 | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:402:28 + --> tests/ui/needless_lifetimes.rs:403:28 | LL | fn pointer_fn_elidable<'a>(i: &'a i32, f: fn(&i32) -> &i32) -> &'a i32 { | ^^ ^^ ^^ @@ -408,7 +408,7 @@ LL + fn pointer_fn_elidable(i: &i32, f: fn(&i32) -> &i32) -> &i32 { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:415:28 + --> tests/ui/needless_lifetimes.rs:416:28 | LL | fn nested_fn_pointer_3<'a>(_: &'a i32) -> fn(fn(&i32) -> &i32) -> i32 { | ^^ ^^ @@ -420,7 +420,7 @@ LL + fn nested_fn_pointer_3(_: &i32) -> fn(fn(&i32) -> &i32) -> i32 { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:418:28 + --> tests/ui/needless_lifetimes.rs:419:28 | LL | fn nested_fn_pointer_4<'a>(_: &'a i32) -> impl Fn(fn(&i32)) { | ^^ ^^ @@ -432,7 +432,7 @@ LL + fn nested_fn_pointer_4(_: &i32) -> impl Fn(fn(&i32)) { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:440:21 + --> tests/ui/needless_lifetimes.rs:441:21 | LL | fn implicit<'a>(&'a self) -> &'a () { | ^^ ^^ ^^ @@ -444,7 +444,7 @@ LL + fn implicit(&self) -> &() { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:443:25 + --> tests/ui/needless_lifetimes.rs:444:25 | LL | fn implicit_mut<'a>(&'a mut self) -> &'a () { | ^^ ^^ ^^ @@ -456,7 +456,31 @@ LL + fn implicit_mut(&mut self) -> &() { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:454:31 + --> tests/ui/needless_lifetimes.rs:448:21 + | +LL | fn explicit<'a>(self: &'a Arc) -> &'a () { + | ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - fn explicit<'a>(self: &'a Arc) -> &'a () { +LL + fn explicit(self: &Arc) -> &() { + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/needless_lifetimes.rs:452:25 + | +LL | fn explicit_mut<'a>(self: &'a mut Rc) -> &'a () { + | ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - fn explicit_mut<'a>(self: &'a mut Rc) -> &'a () { +LL + fn explicit_mut(self: &mut Rc) -> &() { + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/needless_lifetimes.rs:464:31 | LL | fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a () { | ^^ ^^ ^^ @@ -468,7 +492,7 @@ LL + fn lifetime_elsewhere(self: Box, here: &()) -> &() { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:460:21 + --> tests/ui/needless_lifetimes.rs:470:21 | LL | fn implicit<'a>(&'a self) -> &'a (); | ^^ ^^ ^^ @@ -480,7 +504,7 @@ LL + fn implicit(&self) -> &(); | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:461:30 + --> tests/ui/needless_lifetimes.rs:471:30 | LL | fn implicit_provided<'a>(&'a self) -> &'a () { | ^^ ^^ ^^ @@ -492,7 +516,31 @@ LL + fn implicit_provided(&self) -> &() { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:470:31 + --> tests/ui/needless_lifetimes.rs:476:21 + | +LL | fn explicit<'a>(self: &'a Arc) -> &'a (); + | ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - fn explicit<'a>(self: &'a Arc) -> &'a (); +LL + fn explicit(self: &Arc) -> &(); + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/needless_lifetimes.rs:478:30 + | +LL | fn explicit_provided<'a>(self: &'a Arc) -> &'a () { + | ^^ ^^ ^^ + | +help: elide the lifetimes + | +LL - fn explicit_provided<'a>(self: &'a Arc) -> &'a () { +LL + fn explicit_provided(self: &Arc) -> &() { + | + +error: the following explicit lifetimes could be elided: 'a + --> tests/ui/needless_lifetimes.rs:488:31 | LL | fn lifetime_elsewhere<'a>(self: Box, here: &'a ()) -> &'a (); | ^^ ^^ ^^ @@ -504,7 +552,7 @@ LL + fn lifetime_elsewhere(self: Box, here: &()) -> &(); | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:471:40 + --> tests/ui/needless_lifetimes.rs:489:40 | LL | fn lifetime_elsewhere_provided<'a>(self: Box, here: &'a ()) -> &'a () { | ^^ ^^ ^^ @@ -516,7 +564,7 @@ LL + fn lifetime_elsewhere_provided(self: Box, here: &()) -> &() { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:480:12 + --> tests/ui/needless_lifetimes.rs:498:12 | LL | fn foo<'a>(x: &'a u8, y: &'_ u8) {} | ^^ ^^ @@ -528,7 +576,7 @@ LL + fn foo(x: &u8, y: &'_ u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:482:12 + --> tests/ui/needless_lifetimes.rs:500:12 | LL | fn bar<'a>(x: &'a u8, y: &'_ u8, z: &'_ u8) {} | ^^ ^^ @@ -540,7 +588,7 @@ LL + fn bar(x: &u8, y: &'_ u8, z: &'_ u8) {} | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:489:18 + --> tests/ui/needless_lifetimes.rs:507:18 | LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { | ^^ ^^ ^^ @@ -552,7 +600,7 @@ LL + fn one_input(x: &u8) -> &u8 { | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:494:42 + --> tests/ui/needless_lifetimes.rs:512:42 | LL | fn multiple_inputs_output_not_elided<'a, 'b>(x: &'a u8, y: &'b u8, z: &'b u8) -> &'b u8 { | ^^ ^^ @@ -564,7 +612,7 @@ LL + fn multiple_inputs_output_not_elided<'b>(x: &u8, y: &'b u8, z: &'b u8) | error: the following explicit lifetimes could be elided: 'a - --> tests/ui/needless_lifetimes.rs:510:22 + --> tests/ui/needless_lifetimes.rs:528:22 | LL | fn one_input<'a>(x: &'a u8) -> &'a u8 { | ^^ ^^ ^^ @@ -576,5 +624,64 @@ LL - fn one_input<'a>(x: &'a u8) -> &'a u8 { LL + fn one_input(x: &u8) -> &u8 { | -error: aborting due to 48 previous errors +error: the following explicit lifetimes could be elided: 'py + --> tests/ui/needless_lifetimes.rs:623:14 + | +LL | impl<'t, 'py> ContentString<'t> { + | ^^^ +LL | // `'py` can be elided because of `&self` +LL | fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + | ^^^ + | +help: elide the lifetimes + | +LL ~ impl<'t> ContentString<'t> { +LL | // `'py` can be elided because of `&self` +LL ~ fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> { + | + +error: the following explicit lifetimes could be elided: 'py + --> tests/ui/needless_lifetimes.rs:633:14 + | +LL | impl<'t, 'py> ContentString<'t> { + | ^^^ +LL | // `'py` can be elided because of `&'_ self` +LL | fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> { + | ^^^ + | +help: elide the lifetimes + | +LL ~ impl<'t> ContentString<'t> { +LL | // `'py` can be elided because of `&'_ self` +LL ~ fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> { + | + +error: the following explicit lifetimes could be elided: 'py + --> tests/ui/needless_lifetimes.rs:653:14 + | +LL | impl<'t, 'py> ContentString<'t> { + | ^^^ +... +LL | ) -> Content<'t, 'py> { + | ^^^ + | +help: elide the lifetimes + | +LL ~ impl<'t> ContentString<'t> { +LL | // `'py` can be elided because of `&Self` +... +LL | o: &'t str, +LL ~ ) -> Content<'t, '_> { + | + +error: this expression creates a reference which is immediately dereferenced by the compiler + --> tests/ui/needless_lifetimes.rs:674:9 + | +LL | &x.b + | ^^^^ help: change this to: `x.b` + | + = note: `-D clippy::needless-borrow` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_borrow)]` + +error: aborting due to 56 previous errors diff --git a/tests/ui/non_std_lazy_static/auxiliary/lazy_static.rs b/tests/ui/non_std_lazy_static/auxiliary/lazy_static.rs new file mode 100644 index 000000000000..85fb4e66079f --- /dev/null +++ b/tests/ui/non_std_lazy_static/auxiliary/lazy_static.rs @@ -0,0 +1,20 @@ +//! **FAKE** lazy_static crate. + +#[macro_export] +macro_rules! lazy_static { + (static ref $N:ident : $T:ty = $e:expr; $($t:tt)*) => { + static $N : &::core::marker::PhantomData<$T> = &::core::marker::PhantomData; + + $crate::lazy_static! { $($t)* } + }; + () => () +} + +#[macro_export] +macro_rules! external { + () => { + $crate::lazy_static! { + static ref LZ_DERP: u32 = 12; + } + }; +} diff --git a/tests/ui/non_std_lazy_static/auxiliary/once_cell.rs b/tests/ui/non_std_lazy_static/auxiliary/once_cell.rs new file mode 100644 index 000000000000..e860a0f7572c --- /dev/null +++ b/tests/ui/non_std_lazy_static/auxiliary/once_cell.rs @@ -0,0 +1,55 @@ +//! **FAKE** once_cell crate. + +pub mod sync { + use std::marker::PhantomData; + + pub struct Lazy T> { + cell: PhantomData, + init: F, + } + unsafe impl Sync for Lazy {} + impl Lazy { + pub const fn new(f: F) -> Lazy { + Lazy { + cell: PhantomData, + init: f, + } + } + + pub fn into_value(this: Lazy) -> Result { + unimplemented!() + } + + pub fn force(_this: &Lazy) -> &T { + unimplemented!() + } + + pub fn force_mut(_this: &mut Lazy) -> &mut T { + unimplemented!() + } + + pub fn get(_this: &Lazy) -> Option<&T> { + unimplemented!() + } + + pub fn get_mut(_this: &mut Lazy) -> Option<&mut T> { + unimplemented!() + } + } +} +pub mod race { + pub struct OnceBox(T); + + impl OnceBox { + pub fn get(&self) -> Option<&T> { + Some(&self.0) + } + } +} + +#[macro_export] +macro_rules! external { + () => { + static OC_DERP: $crate::sync::Lazy = $crate::sync::Lazy::new(|| 12); + }; +} diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.fixed b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.fixed new file mode 100644 index 000000000000..f7c56b6fffe8 --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.fixed @@ -0,0 +1,72 @@ +//@aux-build:once_cell.rs +//@aux-build:lazy_static.rs + +#![warn(clippy::non_std_lazy_statics)] +#![allow(static_mut_refs)] + +use once_cell::sync::Lazy; + +fn main() {} + +static LAZY_FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "foo".to_uppercase()); +//~^ ERROR: this type has been superceded by `LazyLock` in the standard library +static LAZY_BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| { + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + let x = "bar"; + x.to_uppercase() +}); +static LAZY_BAZ: std::sync::LazyLock = { std::sync::LazyLock::new(|| "baz".to_uppercase()) }; +//~^ ERROR: this type has been superceded by `LazyLock` in the standard library +static LAZY_QUX: std::sync::LazyLock = { + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + if "qux".len() == 3 { + std::sync::LazyLock::new(|| "qux".to_uppercase()) + } else if "qux".is_ascii() { + std::sync::LazyLock::new(|| "qux".to_lowercase()) + } else { + std::sync::LazyLock::new(|| "qux".to_string()) + } +}; + +fn non_static() { + let _: Lazy = Lazy::new(|| 1); + let _: Lazy = Lazy::new(|| String::from("hello")); + #[allow(clippy::declare_interior_mutable_const)] + const DONT_DO_THIS: Lazy = Lazy::new(|| 1); +} + +mod once_cell_lazy_with_fns { + use once_cell::sync::Lazy; + + static LAZY_FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "foo".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static LAZY_BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| "bar".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static mut LAZY_BAZ: std::sync::LazyLock = std::sync::LazyLock::new(|| "baz".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + + fn calling_replaceable_fns() { + let _ = std::sync::LazyLock::force(&LAZY_FOO); + let _ = std::sync::LazyLock::force(&LAZY_BAR); + unsafe { + let _ = std::sync::LazyLock::force(&LAZY_BAZ); + } + } +} + +#[clippy::msrv = "1.79"] +mod msrv_not_meet { + use lazy_static::lazy_static; + use once_cell::sync::Lazy; + + static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + + lazy_static! { + static ref LAZY_BAZ: f64 = 12.159 * 548; + } +} + +mod external_macros { + once_cell::external!(); + lazy_static::external!(); +} diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs new file mode 100644 index 000000000000..90bc428137ce --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs @@ -0,0 +1,72 @@ +//@aux-build:once_cell.rs +//@aux-build:lazy_static.rs + +#![warn(clippy::non_std_lazy_statics)] +#![allow(static_mut_refs)] + +use once_cell::sync::Lazy; + +fn main() {} + +static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); +//~^ ERROR: this type has been superceded by `LazyLock` in the standard library +static LAZY_BAR: Lazy = Lazy::new(|| { + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + let x = "bar"; + x.to_uppercase() +}); +static LAZY_BAZ: Lazy = { Lazy::new(|| "baz".to_uppercase()) }; +//~^ ERROR: this type has been superceded by `LazyLock` in the standard library +static LAZY_QUX: Lazy = { + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + if "qux".len() == 3 { + Lazy::new(|| "qux".to_uppercase()) + } else if "qux".is_ascii() { + Lazy::new(|| "qux".to_lowercase()) + } else { + Lazy::new(|| "qux".to_string()) + } +}; + +fn non_static() { + let _: Lazy = Lazy::new(|| 1); + let _: Lazy = Lazy::new(|| String::from("hello")); + #[allow(clippy::declare_interior_mutable_const)] + const DONT_DO_THIS: Lazy = Lazy::new(|| 1); +} + +mod once_cell_lazy_with_fns { + use once_cell::sync::Lazy; + + static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static LAZY_BAR: Lazy = Lazy::new(|| "bar".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static mut LAZY_BAZ: Lazy = Lazy::new(|| "baz".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + + fn calling_replaceable_fns() { + let _ = Lazy::force(&LAZY_FOO); + let _ = Lazy::force(&LAZY_BAR); + unsafe { + let _ = Lazy::force(&LAZY_BAZ); + } + } +} + +#[clippy::msrv = "1.79"] +mod msrv_not_meet { + use lazy_static::lazy_static; + use once_cell::sync::Lazy; + + static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + + lazy_static! { + static ref LAZY_BAZ: f64 = 12.159 * 548; + } +} + +mod external_macros { + once_cell::external!(); + lazy_static::external!(); +} diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.stderr b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.stderr new file mode 100644 index 000000000000..f956f4b8d52a --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.stderr @@ -0,0 +1,100 @@ +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:11:18 + | +LL | static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + | ^^^^ + | + = note: `-D clippy::non-std-lazy-statics` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::non_std_lazy_statics)]` +help: use `std::sync::LazyLock` instead + | +LL | static LAZY_FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "foo".to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:13:18 + | +LL | static LAZY_BAR: Lazy = Lazy::new(|| { + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL | static LAZY_BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| { + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:18:18 + | +LL | static LAZY_BAZ: Lazy = { Lazy::new(|| "baz".to_uppercase()) }; + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL | static LAZY_BAZ: std::sync::LazyLock = { std::sync::LazyLock::new(|| "baz".to_uppercase()) }; + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:20:18 + | +LL | static LAZY_QUX: Lazy = { + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL ~ static LAZY_QUX: std::sync::LazyLock = { +LL | +LL | if "qux".len() == 3 { +LL ~ std::sync::LazyLock::new(|| "qux".to_uppercase()) +LL | } else if "qux".is_ascii() { +LL ~ std::sync::LazyLock::new(|| "qux".to_lowercase()) +LL | } else { +LL ~ std::sync::LazyLock::new(|| "qux".to_string()) + | + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:41:22 + | +LL | static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL ~ static LAZY_FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "foo".to_uppercase()); +LL | +... +LL | fn calling_replaceable_fns() { +LL ~ let _ = std::sync::LazyLock::force(&LAZY_FOO); + | + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:43:22 + | +LL | static LAZY_BAR: Lazy = Lazy::new(|| "bar".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL ~ static LAZY_BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| "bar".to_uppercase()); +LL | +... +LL | let _ = Lazy::force(&LAZY_FOO); +LL ~ let _ = std::sync::LazyLock::force(&LAZY_BAR); + | + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_fixable.rs:45:26 + | +LL | static mut LAZY_BAZ: Lazy = Lazy::new(|| "baz".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL ~ static mut LAZY_BAZ: std::sync::LazyLock = std::sync::LazyLock::new(|| "baz".to_uppercase()); +LL | +... +LL | unsafe { +LL ~ let _ = std::sync::LazyLock::force(&LAZY_BAZ); + | + +error: aborting due to 7 previous errors + diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_no_std.rs b/tests/ui/non_std_lazy_static/non_std_lazy_static_no_std.rs new file mode 100644 index 000000000000..6208612c6983 --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_no_std.rs @@ -0,0 +1,20 @@ +//@aux-build:once_cell.rs +//@aux-build:lazy_static.rs + +#![warn(clippy::non_std_lazy_statics)] +#![no_std] + +use lazy_static::lazy_static; +use once_cell::sync::Lazy; + +fn main() {} + +static LAZY_FOO: Lazy = Lazy::new(|| 42); +static LAZY_BAR: Lazy = Lazy::new(|| { + let x: i32 = 0; + x.saturating_add(100) +}); + +lazy_static! { + static ref LAZY_BAZ: f64 = 12.159 * 548; +} diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_other_once_cell.rs b/tests/ui/non_std_lazy_static/non_std_lazy_static_other_once_cell.rs new file mode 100644 index 000000000000..8701a4b7729a --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_other_once_cell.rs @@ -0,0 +1,12 @@ +//@aux-build:once_cell.rs + +#![warn(clippy::non_std_lazy_statics)] + +// Should not error, since we used a type besides `sync::Lazy` +fn use_once_cell_race(x: once_cell::race::OnceBox) { + let _foo = x.get(); +} + +use once_cell::sync::Lazy; + +static LAZY_BAZ: Lazy = Lazy::new(|| "baz".to_uppercase()); diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs b/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs new file mode 100644 index 000000000000..34f8dd1ccb2e --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs @@ -0,0 +1,43 @@ +//@aux-build:once_cell.rs +//@aux-build:lazy_static.rs +//@no-rustfix + +#![warn(clippy::non_std_lazy_statics)] +#![allow(static_mut_refs)] + +mod once_cell_lazy { + use once_cell::sync::Lazy; + + static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static mut LAZY_BAR: Lazy = Lazy::new(|| "bar".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + static mut LAZY_BAZ: Lazy = Lazy::new(|| "baz".to_uppercase()); + //~^ ERROR: this type has been superceded by `LazyLock` in the standard library + + fn calling_irreplaceable_fns() { + let _ = Lazy::get(&LAZY_FOO); + + unsafe { + let _ = Lazy::get_mut(&mut LAZY_BAR); + let _ = Lazy::force_mut(&mut LAZY_BAZ); + } + } +} + +mod lazy_static_lazy_static { + use lazy_static::lazy_static; + + lazy_static! { + static ref LAZY_FOO: String = "foo".to_uppercase(); + } + //~^^^ ERROR: this macro has been superceded by `std::sync::LazyLock` + lazy_static! { + static ref LAZY_BAR: String = "bar".to_uppercase(); + static ref LAZY_BAZ: String = "baz".to_uppercase(); + } + //~^^^^ ERROR: this macro has been superceded by `std::sync::LazyLock` + //~| ERROR: this macro has been superceded by `std::sync::LazyLock` +} + +fn main() {} diff --git a/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.stderr b/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.stderr new file mode 100644 index 000000000000..66dc435f9823 --- /dev/null +++ b/tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.stderr @@ -0,0 +1,66 @@ +error: this macro has been superceded by `std::sync::LazyLock` + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:31:5 + | +LL | / lazy_static! { +LL | | static ref LAZY_FOO: String = "foo".to_uppercase(); +LL | | } + | |_____^ + | + = note: `-D clippy::non-std-lazy-statics` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::non_std_lazy_statics)]` + +error: this macro has been superceded by `std::sync::LazyLock` + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:35:5 + | +LL | / lazy_static! { +LL | | static ref LAZY_BAR: String = "bar".to_uppercase(); +LL | | static ref LAZY_BAZ: String = "baz".to_uppercase(); +LL | | } + | |_____^ + +error: this macro has been superceded by `std::sync::LazyLock` + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:35:5 + | +LL | / lazy_static! { +LL | | static ref LAZY_BAR: String = "bar".to_uppercase(); +LL | | static ref LAZY_BAZ: String = "baz".to_uppercase(); +LL | | } + | |_____^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:11:22 + | +LL | static LAZY_FOO: Lazy = Lazy::new(|| "foo".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL | static LAZY_FOO: std::sync::LazyLock = std::sync::LazyLock::new(|| "foo".to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:13:26 + | +LL | static mut LAZY_BAR: Lazy = Lazy::new(|| "bar".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL | static mut LAZY_BAR: std::sync::LazyLock = std::sync::LazyLock::new(|| "bar".to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: this type has been superceded by `LazyLock` in the standard library + --> tests/ui/non_std_lazy_static/non_std_lazy_static_unfixable.rs:15:26 + | +LL | static mut LAZY_BAZ: Lazy = Lazy::new(|| "baz".to_uppercase()); + | ^^^^ + | +help: use `std::sync::LazyLock` instead + | +LL | static mut LAZY_BAZ: std::sync::LazyLock = std::sync::LazyLock::new(|| "baz".to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 6 previous errors + diff --git a/tests/ui/obfuscated_if_else.fixed b/tests/ui/obfuscated_if_else.fixed index c5ee569800ab..bfe1c5e10cf8 100644 --- a/tests/ui/obfuscated_if_else.fixed +++ b/tests/ui/obfuscated_if_else.fixed @@ -1,5 +1,18 @@ #![warn(clippy::obfuscated_if_else)] +#![allow(clippy::unnecessary_lazy_evaluations, clippy::unit_arg, clippy::unused_unit)] fn main() { if true { "a" } else { "b" }; + if true { "a" } else { "b" }; + + let a = 1; + if a == 1 { "a" } else { "b" }; + if a == 1 { "a" } else { "b" }; + + let partial = (a == 1).then_some("a"); + partial.unwrap_or("b"); // not lint + + let mut a = 0; + if true { a += 1 } else { () }; + if true { () } else { a += 2 }; } diff --git a/tests/ui/obfuscated_if_else.rs b/tests/ui/obfuscated_if_else.rs index 2b60c855a555..0ded2a2ceedf 100644 --- a/tests/ui/obfuscated_if_else.rs +++ b/tests/ui/obfuscated_if_else.rs @@ -1,5 +1,18 @@ #![warn(clippy::obfuscated_if_else)] +#![allow(clippy::unnecessary_lazy_evaluations, clippy::unit_arg, clippy::unused_unit)] fn main() { true.then_some("a").unwrap_or("b"); + true.then(|| "a").unwrap_or("b"); + + let a = 1; + (a == 1).then_some("a").unwrap_or("b"); + (a == 1).then(|| "a").unwrap_or("b"); + + let partial = (a == 1).then_some("a"); + partial.unwrap_or("b"); // not lint + + let mut a = 0; + true.then_some(a += 1).unwrap_or(()); + true.then_some(()).unwrap_or(a += 2); } diff --git a/tests/ui/obfuscated_if_else.stderr b/tests/ui/obfuscated_if_else.stderr index d4c2f9b331a8..9ce1f475c480 100644 --- a/tests/ui/obfuscated_if_else.stderr +++ b/tests/ui/obfuscated_if_else.stderr @@ -1,5 +1,5 @@ -error: use of `.then_some(..).unwrap_or(..)` can be written more clearly with `if .. else ..` - --> tests/ui/obfuscated_if_else.rs:4:5 +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:5:5 | LL | true.then_some("a").unwrap_or("b"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { "a" } else { "b" }` @@ -7,5 +7,35 @@ LL | true.then_some("a").unwrap_or("b"); = note: `-D clippy::obfuscated-if-else` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::obfuscated_if_else)]` -error: aborting due to 1 previous error +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:6:5 + | +LL | true.then(|| "a").unwrap_or("b"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { "a" } else { "b" }` + +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:9:5 + | +LL | (a == 1).then_some("a").unwrap_or("b"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if a == 1 { "a" } else { "b" }` + +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:10:5 + | +LL | (a == 1).then(|| "a").unwrap_or("b"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if a == 1 { "a" } else { "b" }` + +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:16:5 + | +LL | true.then_some(a += 1).unwrap_or(()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { a += 1 } else { () }` + +error: this method chain can be written more clearly with `if .. else ..` + --> tests/ui/obfuscated_if_else.rs:17:5 + | +LL | true.then_some(()).unwrap_or(a += 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { () } else { a += 2 }` + +error: aborting due to 6 previous errors diff --git a/tests/ui/precedence.stderr b/tests/ui/precedence.stderr index 0d63e827d66e..329422cb8a69 100644 --- a/tests/ui/precedence.stderr +++ b/tests/ui/precedence.stderr @@ -1,4 +1,4 @@ -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:16:5 | LL | 1 << 2 + 3; @@ -7,61 +7,61 @@ LL | 1 << 2 + 3; = note: `-D clippy::precedence` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::precedence)]` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:17:5 | LL | 1 + 2 << 3; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 2) << 3` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:18:5 | LL | 4 >> 1 + 1; | ^^^^^^^^^^ help: consider parenthesizing your expression: `4 >> (1 + 1)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:19:5 | LL | 1 + 3 >> 2; | ^^^^^^^^^^ help: consider parenthesizing your expression: `(1 + 3) >> 2` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:20:5 | LL | 1 ^ 1 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `1 ^ (1 - 1)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:21:5 | LL | 3 | 2 - 1; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 | (2 - 1)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:22:5 | LL | 3 & 5 - 2; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:23:5 | LL | 0x0F00 & 0x00F0 << 4; | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:24:5 | LL | 0x0F00 & 0xF000 >> 4; | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:25:5 | LL | 0x0F00 << 1 ^ 3; | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3` -error: operator precedence can trip the unwary +error: operator precedence might not be obvious --> tests/ui/precedence.rs:26:5 | LL | 0x0F00 << 1 | 2; diff --git a/tests/ui/redundant_pub_crate.fixed b/tests/ui/redundant_pub_crate.fixed index e1d845721a9c..8882a4d50a5f 100644 --- a/tests/ui/redundant_pub_crate.fixed +++ b/tests/ui/redundant_pub_crate.fixed @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![allow(dead_code)] #![warn(clippy::redundant_pub_crate)] @@ -113,4 +114,10 @@ mod issue_8732 { pub(crate) use some_macro; // ok: macro exports are exempt } +proc_macros::external! { + mod priv_mod { + pub(crate) fn dummy() {} + } +} + fn main() {} diff --git a/tests/ui/redundant_pub_crate.rs b/tests/ui/redundant_pub_crate.rs index 4d7f44892d0c..5c8cab9be161 100644 --- a/tests/ui/redundant_pub_crate.rs +++ b/tests/ui/redundant_pub_crate.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![allow(dead_code)] #![warn(clippy::redundant_pub_crate)] @@ -113,4 +114,10 @@ mod issue_8732 { pub(crate) use some_macro; // ok: macro exports are exempt } +proc_macros::external! { + mod priv_mod { + pub(crate) fn dummy() {} + } +} + fn main() {} diff --git a/tests/ui/redundant_pub_crate.stderr b/tests/ui/redundant_pub_crate.stderr index 8f1005ab9b73..699e19b1abcf 100644 --- a/tests/ui/redundant_pub_crate.stderr +++ b/tests/ui/redundant_pub_crate.stderr @@ -1,5 +1,5 @@ error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:6:5 + --> tests/ui/redundant_pub_crate.rs:7:5 | LL | pub(crate) fn g() {} // private due to m1 | ----------^^^^^ @@ -10,7 +10,7 @@ LL | pub(crate) fn g() {} // private due to m1 = help: to override `-D warnings` add `#[allow(clippy::redundant_pub_crate)]` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:11:9 + --> tests/ui/redundant_pub_crate.rs:12:9 | LL | pub(crate) fn g() {} // private due to m1_1 and m1 | ----------^^^^^ @@ -18,7 +18,7 @@ LL | pub(crate) fn g() {} // private due to m1_1 and m1 | help: consider using: `pub` error: pub(crate) module inside private module - --> tests/ui/redundant_pub_crate.rs:15:5 + --> tests/ui/redundant_pub_crate.rs:16:5 | LL | pub(crate) mod m1_2 { | ----------^^^^^^^^^ @@ -26,7 +26,7 @@ LL | pub(crate) mod m1_2 { | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:18:9 + --> tests/ui/redundant_pub_crate.rs:19:9 | LL | pub(crate) fn g() {} // private due to m1_2 and m1 | ----------^^^^^ @@ -34,7 +34,7 @@ LL | pub(crate) fn g() {} // private due to m1_2 and m1 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:24:9 + --> tests/ui/redundant_pub_crate.rs:25:9 | LL | pub(crate) fn g() {} // private due to m1 | ----------^^^^^ @@ -42,7 +42,7 @@ LL | pub(crate) fn g() {} // private due to m1 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:31:5 + --> tests/ui/redundant_pub_crate.rs:32:5 | LL | pub(crate) fn g() {} // already crate visible due to m2 | ----------^^^^^ @@ -50,7 +50,7 @@ LL | pub(crate) fn g() {} // already crate visible due to m2 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:36:9 + --> tests/ui/redundant_pub_crate.rs:37:9 | LL | pub(crate) fn g() {} // private due to m2_1 | ----------^^^^^ @@ -58,7 +58,7 @@ LL | pub(crate) fn g() {} // private due to m2_1 | help: consider using: `pub` error: pub(crate) module inside private module - --> tests/ui/redundant_pub_crate.rs:40:5 + --> tests/ui/redundant_pub_crate.rs:41:5 | LL | pub(crate) mod m2_2 { | ----------^^^^^^^^^ @@ -66,7 +66,7 @@ LL | pub(crate) mod m2_2 { | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:43:9 + --> tests/ui/redundant_pub_crate.rs:44:9 | LL | pub(crate) fn g() {} // already crate visible due to m2_2 and m2 | ----------^^^^^ @@ -74,7 +74,7 @@ LL | pub(crate) fn g() {} // already crate visible due to m2_2 and m2 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:49:9 + --> tests/ui/redundant_pub_crate.rs:50:9 | LL | pub(crate) fn g() {} // already crate visible due to m2 | ----------^^^^^ @@ -82,7 +82,7 @@ LL | pub(crate) fn g() {} // already crate visible due to m2 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:61:9 + --> tests/ui/redundant_pub_crate.rs:62:9 | LL | pub(crate) fn g() {} // private due to m3_1 | ----------^^^^^ @@ -90,7 +90,7 @@ LL | pub(crate) fn g() {} // private due to m3_1 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:68:9 + --> tests/ui/redundant_pub_crate.rs:69:9 | LL | pub(crate) fn g() {} // already crate visible due to m3_2 | ----------^^^^^ @@ -98,7 +98,7 @@ LL | pub(crate) fn g() {} // already crate visible due to m3_2 | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:81:5 + --> tests/ui/redundant_pub_crate.rs:82:5 | LL | pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` | ----------^^^^^ @@ -106,7 +106,7 @@ LL | pub(crate) fn g() {} // private: not re-exported by `pub use m4::*` | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:86:9 + --> tests/ui/redundant_pub_crate.rs:87:9 | LL | pub(crate) fn g() {} // private due to m4_1 | ----------^^^^^ @@ -114,7 +114,7 @@ LL | pub(crate) fn g() {} // private due to m4_1 | help: consider using: `pub` error: pub(crate) module inside private module - --> tests/ui/redundant_pub_crate.rs:90:5 + --> tests/ui/redundant_pub_crate.rs:91:5 | LL | pub(crate) mod m4_2 { | ----------^^^^^^^^^ @@ -122,7 +122,7 @@ LL | pub(crate) mod m4_2 { | help: consider using: `pub` error: pub(crate) function inside private module - --> tests/ui/redundant_pub_crate.rs:93:9 + --> tests/ui/redundant_pub_crate.rs:94:9 | LL | pub(crate) fn g() {} // private due to m4_2 | ----------^^^^^ diff --git a/tests/ui/repeat_vec_with_capacity_nostd.fixed b/tests/ui/repeat_vec_with_capacity_nostd.fixed new file mode 100644 index 000000000000..ef316f1def41 --- /dev/null +++ b/tests/ui/repeat_vec_with_capacity_nostd.fixed @@ -0,0 +1,10 @@ +#![warn(clippy::repeat_vec_with_capacity)] +#![allow(clippy::manual_repeat_n)] +#![no_std] +use core::iter; +extern crate alloc; +use alloc::vec::Vec; + +fn nostd() { + let _: Vec> = core::iter::repeat_with(|| Vec::with_capacity(42)).take(123).collect(); +} diff --git a/tests/ui/repeat_vec_with_capacity_nostd.rs b/tests/ui/repeat_vec_with_capacity_nostd.rs new file mode 100644 index 000000000000..83b418a56674 --- /dev/null +++ b/tests/ui/repeat_vec_with_capacity_nostd.rs @@ -0,0 +1,10 @@ +#![warn(clippy::repeat_vec_with_capacity)] +#![allow(clippy::manual_repeat_n)] +#![no_std] +use core::iter; +extern crate alloc; +use alloc::vec::Vec; + +fn nostd() { + let _: Vec> = iter::repeat(Vec::with_capacity(42)).take(123).collect(); +} diff --git a/tests/ui/repeat_vec_with_capacity_nostd.stderr b/tests/ui/repeat_vec_with_capacity_nostd.stderr new file mode 100644 index 000000000000..39364d09b961 --- /dev/null +++ b/tests/ui/repeat_vec_with_capacity_nostd.stderr @@ -0,0 +1,16 @@ +error: repeating `Vec::with_capacity` using `iter::repeat`, which does not retain capacity + --> tests/ui/repeat_vec_with_capacity_nostd.rs:9:27 + | +LL | let _: Vec> = iter::repeat(Vec::with_capacity(42)).take(123).collect(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: none of the yielded `Vec`s will have the requested capacity + = note: `-D clippy::repeat-vec-with-capacity` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::repeat_vec_with_capacity)]` +help: if you intended to create an iterator that yields `Vec`s with an initial capacity, try + | +LL | let _: Vec> = core::iter::repeat_with(|| Vec::with_capacity(42)).take(123).collect(); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 1 previous error + diff --git a/tests/ui/same_item_push.rs b/tests/ui/same_item_push.rs index df9c2817f508..87fd59ad3179 100644 --- a/tests/ui/same_item_push.rs +++ b/tests/ui/same_item_push.rs @@ -21,33 +21,43 @@ fn main() { let item = 2; for _ in 5..=20 { vec.push(item); - //~^ ERROR: it looks like the same item is being pushed into this Vec + //~^ ERROR: it looks like the same item is being pushed into this `Vec` } let mut vec: Vec = Vec::new(); for _ in 0..15 { let item = 2; vec.push(item); - //~^ ERROR: it looks like the same item is being pushed into this Vec + //~^ ERROR: it looks like the same item is being pushed into this `Vec` } let mut vec: Vec = Vec::new(); for _ in 0..15 { vec.push(13); - //~^ ERROR: it looks like the same item is being pushed into this Vec + //~^ ERROR: it looks like the same item is being pushed into this `Vec` } let mut vec = Vec::new(); for _ in 0..20 { vec.push(VALUE); - //~^ ERROR: it looks like the same item is being pushed into this Vec + //~^ ERROR: it looks like the same item is being pushed into this `Vec` } let mut vec = Vec::new(); let item = VALUE; for _ in 0..20 { vec.push(item); - //~^ ERROR: it looks like the same item is being pushed into this Vec + //~^ ERROR: it looks like the same item is being pushed into this `Vec` + } + + #[clippy::msrv = "1.81"] + fn older_msrv() { + let mut vec = Vec::new(); + let item = VALUE; + for _ in 0..20 { + vec.push(item); + //~^ ERROR: it looks like the same item is being pushed into this `Vec` + } } // ** non-linted cases ** diff --git a/tests/ui/same_item_push.stderr b/tests/ui/same_item_push.stderr index eb296ed4ce4a..e3fa4f9cbcec 100644 --- a/tests/ui/same_item_push.stderr +++ b/tests/ui/same_item_push.stderr @@ -1,44 +1,58 @@ -error: it looks like the same item is being pushed into this Vec +error: it looks like the same item is being pushed into this `Vec` --> tests/ui/same_item_push.rs:23:9 | LL | vec.push(item); | ^^^ | - = help: consider using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + = help: consider using `vec![item;SIZE]` + = help: or `vec.extend(std::iter::repeat_n(item, SIZE))` = note: `-D clippy::same-item-push` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::same_item_push)]` -error: it looks like the same item is being pushed into this Vec +error: it looks like the same item is being pushed into this `Vec` --> tests/ui/same_item_push.rs:30:9 | LL | vec.push(item); | ^^^ | - = help: consider using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + = help: consider using `vec![item;SIZE]` + = help: or `vec.extend(std::iter::repeat_n(item, SIZE))` -error: it looks like the same item is being pushed into this Vec +error: it looks like the same item is being pushed into this `Vec` --> tests/ui/same_item_push.rs:36:9 | LL | vec.push(13); | ^^^ | - = help: consider using vec![13;SIZE] or vec.resize(NEW_SIZE, 13) + = help: consider using `vec![13;SIZE]` + = help: or `vec.extend(std::iter::repeat_n(13, SIZE))` -error: it looks like the same item is being pushed into this Vec +error: it looks like the same item is being pushed into this `Vec` --> tests/ui/same_item_push.rs:42:9 | LL | vec.push(VALUE); | ^^^ | - = help: consider using vec![VALUE;SIZE] or vec.resize(NEW_SIZE, VALUE) + = help: consider using `vec![VALUE;SIZE]` + = help: or `vec.extend(std::iter::repeat_n(VALUE, SIZE))` -error: it looks like the same item is being pushed into this Vec +error: it looks like the same item is being pushed into this `Vec` --> tests/ui/same_item_push.rs:49:9 | LL | vec.push(item); | ^^^ | - = help: consider using vec![item;SIZE] or vec.resize(NEW_SIZE, item) + = help: consider using `vec![item;SIZE]` + = help: or `vec.extend(std::iter::repeat_n(item, SIZE))` -error: aborting due to 5 previous errors +error: it looks like the same item is being pushed into this `Vec` + --> tests/ui/same_item_push.rs:58:13 + | +LL | vec.push(item); + | ^^^ + | + = help: consider using `vec![item;SIZE]` + = help: or `vec.resize(NEW_SIZE, item)` + +error: aborting due to 6 previous errors diff --git a/tests/ui/short_circuit_statement.fixed b/tests/ui/short_circuit_statement.fixed index a9930ef4dbb6..a2bf07ac6052 100644 --- a/tests/ui/short_circuit_statement.fixed +++ b/tests/ui/short_circuit_statement.fixed @@ -3,8 +3,35 @@ fn main() { if f() { g(); } + //~^ ERROR: boolean short circuit operator in statement if !f() { g(); } + //~^ ERROR: boolean short circuit operator in statement if 1 != 2 { g(); } + //~^ ERROR: boolean short circuit operator in statement + if f() || g() { H * 2; } + //~^ ERROR: boolean short circuit operator in statement + if !(f() || g()) { H * 2; } + //~^ ERROR: boolean short circuit operator in statement + + macro_rules! mac { + ($f:ident or $g:ident) => { + $f() || $g() + }; + ($f:ident and $g:ident) => { + $f() && $g() + }; + () => { + f() && g() + }; + } + + if mac!() { mac!(); } + //~^ ERROR: boolean short circuit operator in statement + if !mac!() { mac!(); } + //~^ ERROR: boolean short circuit operator in statement + + // Do not lint if the expression comes from a macro + mac!(); } fn f() -> bool { @@ -14,3 +41,12 @@ fn f() -> bool { fn g() -> bool { false } + +struct H; + +impl std::ops::Mul for H { + type Output = bool; + fn mul(self, other: u32) -> Self::Output { + true + } +} diff --git a/tests/ui/short_circuit_statement.rs b/tests/ui/short_circuit_statement.rs index 71f7c7f2abf7..bdba546ad8f6 100644 --- a/tests/ui/short_circuit_statement.rs +++ b/tests/ui/short_circuit_statement.rs @@ -3,8 +3,35 @@ fn main() { f() && g(); + //~^ ERROR: boolean short circuit operator in statement f() || g(); + //~^ ERROR: boolean short circuit operator in statement 1 == 2 || g(); + //~^ ERROR: boolean short circuit operator in statement + (f() || g()) && (H * 2); + //~^ ERROR: boolean short circuit operator in statement + (f() || g()) || (H * 2); + //~^ ERROR: boolean short circuit operator in statement + + macro_rules! mac { + ($f:ident or $g:ident) => { + $f() || $g() + }; + ($f:ident and $g:ident) => { + $f() && $g() + }; + () => { + f() && g() + }; + } + + mac!() && mac!(); + //~^ ERROR: boolean short circuit operator in statement + mac!() || mac!(); + //~^ ERROR: boolean short circuit operator in statement + + // Do not lint if the expression comes from a macro + mac!(); } fn f() -> bool { @@ -14,3 +41,12 @@ fn f() -> bool { fn g() -> bool { false } + +struct H; + +impl std::ops::Mul for H { + type Output = bool; + fn mul(self, other: u32) -> Self::Output { + true + } +} diff --git a/tests/ui/short_circuit_statement.stderr b/tests/ui/short_circuit_statement.stderr index e7a8f2ca60ca..ecf6676405b4 100644 --- a/tests/ui/short_circuit_statement.stderr +++ b/tests/ui/short_circuit_statement.stderr @@ -8,16 +8,40 @@ LL | f() && g(); = help: to override `-D warnings` add `#[allow(clippy::short_circuit_statement)]` error: boolean short circuit operator in statement may be clearer using an explicit test - --> tests/ui/short_circuit_statement.rs:6:5 + --> tests/ui/short_circuit_statement.rs:7:5 | LL | f() || g(); | ^^^^^^^^^^^ help: replace it with: `if !f() { g(); }` error: boolean short circuit operator in statement may be clearer using an explicit test - --> tests/ui/short_circuit_statement.rs:7:5 + --> tests/ui/short_circuit_statement.rs:9:5 | LL | 1 == 2 || g(); | ^^^^^^^^^^^^^^ help: replace it with: `if 1 != 2 { g(); }` -error: aborting due to 3 previous errors +error: boolean short circuit operator in statement may be clearer using an explicit test + --> tests/ui/short_circuit_statement.rs:11:5 + | +LL | (f() || g()) && (H * 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `if f() || g() { H * 2; }` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> tests/ui/short_circuit_statement.rs:13:5 + | +LL | (f() || g()) || (H * 2); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `if !(f() || g()) { H * 2; }` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> tests/ui/short_circuit_statement.rs:28:5 + | +LL | mac!() && mac!(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `if mac!() { mac!(); }` + +error: boolean short circuit operator in statement may be clearer using an explicit test + --> tests/ui/short_circuit_statement.rs:30:5 + | +LL | mac!() || mac!(); + | ^^^^^^^^^^^^^^^^^ help: replace it with: `if !mac!() { mac!(); }` + +error: aborting due to 7 previous errors diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 8468d1d7c7d4..39d550398d70 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -832,4 +832,36 @@ fn should_trigger_lint_in_while_let() { } } +async fn foo_async(mutex: &Mutex) -> Option> { + Some(mutex.lock().unwrap()) +} + +async fn should_trigger_lint_for_async(mutex: Mutex) -> i32 { + match *foo_async(&mutex).await.unwrap() { + n if n < 10 => n, + _ => 10, + } +} + +async fn should_not_trigger_lint_in_async_expansion(mutex: Mutex) -> i32 { + match foo_async(&mutex).await { + Some(guard) => *guard, + _ => 0, + } +} + +fn should_trigger_lint_in_match_expr() { + let mutex = Mutex::new(State {}); + + // Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it + // is preserved until the end of the match, but there is no clear indication that this is the + // case. + let _ = match mutex.lock().unwrap().foo() { + //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + true => 0, + false => 1, + }; +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 62030cbe70e7..f99d862aa6b2 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -568,5 +568,37 @@ LL | } | = note: this might lead to deadlocks or other unexpected behavior -error: aborting due to 29 previous errors +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> tests/ui/significant_drop_in_scrutinee.rs:840:11 + | +LL | match *foo_async(&mutex).await.unwrap() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +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 = *foo_async(&mutex).await.unwrap(); +LL ~ match value { + | + +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> tests/ui/significant_drop_in_scrutinee.rs:859:19 + | +LL | let _ = match mutex.lock().unwrap().foo() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +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().foo(); +LL ~ let _ = match value { + | + +error: aborting due to 31 previous errors diff --git a/tests/ui/sliced_string_as_bytes.fixed b/tests/ui/sliced_string_as_bytes.fixed new file mode 100644 index 000000000000..469ad27a99b9 --- /dev/null +++ b/tests/ui/sliced_string_as_bytes.fixed @@ -0,0 +1,34 @@ +#![allow(unused)] +#![warn(clippy::sliced_string_as_bytes)] + +use std::ops::{Index, Range}; + +struct Foo; + +struct Bar; + +impl Bar { + fn as_bytes(&self) -> &[u8] { + &[0, 1, 2, 3] + } +} + +impl Index> for Foo { + type Output = Bar; + + fn index(&self, _: Range) -> &Self::Output { + &Bar + } +} + +fn main() { + let s = "Lorem ipsum"; + let string: String = "dolor sit amet".to_owned(); + + let bytes = &s.as_bytes()[1..5]; + let bytes = &string.as_bytes()[1..]; + let bytes = &"consectetur adipiscing".as_bytes()[..=5]; + + let f = Foo; + let bytes = f[0..4].as_bytes(); +} diff --git a/tests/ui/sliced_string_as_bytes.rs b/tests/ui/sliced_string_as_bytes.rs new file mode 100644 index 000000000000..4a4605e5a1ae --- /dev/null +++ b/tests/ui/sliced_string_as_bytes.rs @@ -0,0 +1,34 @@ +#![allow(unused)] +#![warn(clippy::sliced_string_as_bytes)] + +use std::ops::{Index, Range}; + +struct Foo; + +struct Bar; + +impl Bar { + fn as_bytes(&self) -> &[u8] { + &[0, 1, 2, 3] + } +} + +impl Index> for Foo { + type Output = Bar; + + fn index(&self, _: Range) -> &Self::Output { + &Bar + } +} + +fn main() { + let s = "Lorem ipsum"; + let string: String = "dolor sit amet".to_owned(); + + let bytes = s[1..5].as_bytes(); + let bytes = string[1..].as_bytes(); + let bytes = "consectetur adipiscing"[..=5].as_bytes(); + + let f = Foo; + let bytes = f[0..4].as_bytes(); +} diff --git a/tests/ui/sliced_string_as_bytes.stderr b/tests/ui/sliced_string_as_bytes.stderr new file mode 100644 index 000000000000..1342f4c01a48 --- /dev/null +++ b/tests/ui/sliced_string_as_bytes.stderr @@ -0,0 +1,23 @@ +error: calling `as_bytes` after slicing a string + --> tests/ui/sliced_string_as_bytes.rs:28:17 + | +LL | let bytes = s[1..5].as_bytes(); + | ^^^^^^^^^^^^^^^^^^ help: try: `&s.as_bytes()[1..5]` + | + = note: `-D clippy::sliced-string-as-bytes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::sliced_string_as_bytes)]` + +error: calling `as_bytes` after slicing a string + --> tests/ui/sliced_string_as_bytes.rs:29:17 + | +LL | let bytes = string[1..].as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&string.as_bytes()[1..]` + +error: calling `as_bytes` after slicing a string + --> tests/ui/sliced_string_as_bytes.rs:30:17 + | +LL | let bytes = "consectetur adipiscing"[..=5].as_bytes(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&"consectetur adipiscing".as_bytes()[..=5]` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/slow_vector_initialization.fixed b/tests/ui/slow_vector_initialization.fixed new file mode 100644 index 000000000000..a8570366e646 --- /dev/null +++ b/tests/ui/slow_vector_initialization.fixed @@ -0,0 +1,86 @@ +#![allow(clippy::useless_vec, clippy::manual_repeat_n)] + +use std::iter::repeat; +fn main() { + resize_vector(); + extend_vector(); + mixed_extend_resize_vector(); + from_empty_vec(); +} + +fn extend_vector() { + // Extend with constant expression + let len = 300; + let mut vec1 = vec![0; len]; + + // Extend with len expression + let mut vec2 = vec![0; len - 10]; + + // Extend with mismatching expression should not be warned + let mut vec3 = Vec::with_capacity(24322); + vec3.extend(repeat(0).take(2)); + + let mut vec4 = vec![0; len]; +} + +fn mixed_extend_resize_vector() { + // Mismatching len + let mut mismatching_len = Vec::with_capacity(30); + mismatching_len.extend(repeat(0).take(40)); + + // Slow initialization + let mut resized_vec = vec![0; 30]; + + let mut extend_vec = vec![0; 30]; +} + +fn resize_vector() { + // Resize with constant expression + let len = 300; + let mut vec1 = vec![0; len]; + + // Resize mismatch len + let mut vec2 = Vec::with_capacity(200); + vec2.resize(10, 0); + + // Resize with len expression + let mut vec3 = vec![0; len - 10]; + + let mut vec4 = vec![0; len]; + + // Reinitialization should be warned + vec1 = vec![0; 10]; +} + +fn from_empty_vec() { + // Resize with constant expression + let len = 300; + let mut vec1 = vec![0; len]; + + // Resize with len expression + let mut vec3 = vec![0; len - 10]; + + // Reinitialization should be warned + vec1 = vec![0; 10]; + + vec1 = vec![0; 10]; + + macro_rules! x { + () => { + vec![] + }; + } + + // `vec![]` comes from another macro, don't warn + vec1 = x!(); + vec1.resize(10, 0); +} + +fn do_stuff(vec: &mut [u8]) {} + +fn extend_vector_with_manipulations_between() { + let len = 300; + let mut vec1: Vec = Vec::with_capacity(len); + do_stuff(&mut vec1); + vec1.extend(repeat(0).take(len)); +} diff --git a/tests/ui/slow_vector_initialization.rs b/tests/ui/slow_vector_initialization.rs index 2ba87f412500..4b30fad409e3 100644 --- a/tests/ui/slow_vector_initialization.rs +++ b/tests/ui/slow_vector_initialization.rs @@ -1,4 +1,5 @@ -//@no-rustfix +#![allow(clippy::useless_vec, clippy::manual_repeat_n)] + use std::iter::repeat; fn main() { resize_vector(); diff --git a/tests/ui/slow_vector_initialization.stderr b/tests/ui/slow_vector_initialization.stderr index 7f4b9f7b67a4..4a25cafcddf2 100644 --- a/tests/ui/slow_vector_initialization.stderr +++ b/tests/ui/slow_vector_initialization.stderr @@ -1,5 +1,5 @@ error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:13:20 + --> tests/ui/slow_vector_initialization.rs:14:20 | LL | let mut vec1 = Vec::with_capacity(len); | ____________________^ @@ -11,7 +11,7 @@ LL | | vec1.extend(repeat(0).take(len)); = help: to override `-D warnings` add `#[allow(clippy::slow_vector_initialization)]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:19:20 + --> tests/ui/slow_vector_initialization.rs:20:20 | LL | let mut vec2 = Vec::with_capacity(len - 10); | ____________________^ @@ -20,7 +20,7 @@ LL | | vec2.extend(repeat(0).take(len - 10)); | |_________________________________________^ help: consider replacing this with: `vec![0; len - 10]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:27:20 + --> tests/ui/slow_vector_initialization.rs:28:20 | LL | let mut vec4 = Vec::with_capacity(len); | ____________________^ @@ -29,7 +29,7 @@ LL | | vec4.extend(repeat(0).take(vec4.capacity())); | |________________________________________________^ help: consider replacing this with: `vec![0; len]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:38:27 + --> tests/ui/slow_vector_initialization.rs:39:27 | LL | let mut resized_vec = Vec::with_capacity(30); | ___________________________^ @@ -38,7 +38,7 @@ LL | | resized_vec.resize(30, 0); | |_____________________________^ help: consider replacing this with: `vec![0; 30]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:42:26 + --> tests/ui/slow_vector_initialization.rs:43:26 | LL | let mut extend_vec = Vec::with_capacity(30); | __________________________^ @@ -47,7 +47,7 @@ LL | | extend_vec.extend(repeat(0).take(30)); | |_________________________________________^ help: consider replacing this with: `vec![0; 30]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:50:20 + --> tests/ui/slow_vector_initialization.rs:51:20 | LL | let mut vec1 = Vec::with_capacity(len); | ____________________^ @@ -56,7 +56,7 @@ LL | | vec1.resize(len, 0); | |_______________________^ help: consider replacing this with: `vec![0; len]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:59:20 + --> tests/ui/slow_vector_initialization.rs:60:20 | LL | let mut vec3 = Vec::with_capacity(len - 10); | ____________________^ @@ -65,7 +65,7 @@ LL | | vec3.resize(len - 10, 0); | |____________________________^ help: consider replacing this with: `vec![0; len - 10]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:63:20 + --> tests/ui/slow_vector_initialization.rs:64:20 | LL | let mut vec4 = Vec::with_capacity(len); | ____________________^ @@ -74,7 +74,7 @@ LL | | vec4.resize(vec4.capacity(), 0); | |___________________________________^ help: consider replacing this with: `vec![0; len]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:68:12 + --> tests/ui/slow_vector_initialization.rs:69:12 | LL | vec1 = Vec::with_capacity(10); | ____________^ @@ -83,7 +83,7 @@ LL | | vec1.resize(10, 0); | |______________________^ help: consider replacing this with: `vec![0; 10]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:76:20 + --> tests/ui/slow_vector_initialization.rs:77:20 | LL | let mut vec1 = Vec::new(); | ____________________^ @@ -92,7 +92,7 @@ LL | | vec1.resize(len, 0); | |_______________________^ help: consider replacing this with: `vec![0; len]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:81:20 + --> tests/ui/slow_vector_initialization.rs:82:20 | LL | let mut vec3 = Vec::new(); | ____________________^ @@ -101,7 +101,7 @@ LL | | vec3.resize(len - 10, 0); | |____________________________^ help: consider replacing this with: `vec![0; len - 10]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:86:12 + --> tests/ui/slow_vector_initialization.rs:87:12 | LL | vec1 = Vec::new(); | ____________^ @@ -110,7 +110,7 @@ LL | | vec1.resize(10, 0); | |______________________^ help: consider replacing this with: `vec![0; 10]` error: slow zero-filling initialization - --> tests/ui/slow_vector_initialization.rs:90:12 + --> tests/ui/slow_vector_initialization.rs:91:12 | LL | vec1 = vec![]; | ____________^ diff --git a/tests/ui/unnecessary_map_or.fixed b/tests/ui/unnecessary_map_or.fixed index efea28e7045c..5a6e77a06b8f 100644 --- a/tests/ui/unnecessary_map_or.fixed +++ b/tests/ui/unnecessary_map_or.fixed @@ -82,3 +82,20 @@ fn msrv_1_81() { // is_none_or added in 1.82.0 let _ = Some(5).map_or(true, |n| n == if 2 > 1 { n } else { 0 }); } + +fn with_refs(o: &mut Option) -> bool { + o.is_none_or(|n| n > 5) || (o as &Option).is_none_or(|n| n < 5) +} + +struct S; + +impl std::ops::Deref for S { + type Target = Option; + fn deref(&self) -> &Self::Target { + &Some(0) + } +} + +fn with_deref(o: &S) -> bool { + o.is_none_or(|n| n > 5) +} diff --git a/tests/ui/unnecessary_map_or.rs b/tests/ui/unnecessary_map_or.rs index 05a0ca816ef6..5ba63121659f 100644 --- a/tests/ui/unnecessary_map_or.rs +++ b/tests/ui/unnecessary_map_or.rs @@ -85,3 +85,20 @@ fn msrv_1_81() { // is_none_or added in 1.82.0 let _ = Some(5).map_or(true, |n| n == if 2 > 1 { n } else { 0 }); } + +fn with_refs(o: &mut Option) -> bool { + o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) +} + +struct S; + +impl std::ops::Deref for S { + type Target = Option; + fn deref(&self) -> &Self::Target { + &Some(0) + } +} + +fn with_deref(o: &S) -> bool { + o.map_or(true, |n| n > 5) +} diff --git a/tests/ui/unnecessary_map_or.stderr b/tests/ui/unnecessary_map_or.stderr index 2b78996d5f3e..2ae327f0bf88 100644 --- a/tests/ui/unnecessary_map_or.stderr +++ b/tests/ui/unnecessary_map_or.stderr @@ -2,16 +2,25 @@ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:13:13 | LL | let _ = Some(5).map_or(false, |n| n == 5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `Some(5) == Some(5)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unnecessary-map-or` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_map_or)]` +help: use a standard comparison instead + | +LL | let _ = Some(5) == Some(5); + | ~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:14:13 | LL | let _ = Some(5).map_or(true, |n| n != 5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `Some(5) != Some(5)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = Some(5) != Some(5); + | ~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:15:13 @@ -21,7 +30,12 @@ LL | let _ = Some(5).map_or(false, |n| { LL | | let _ = 1; LL | | n == 5 LL | | }); - | |______^ help: use a standard comparison instead: `Some(5) == Some(5)` + | |______^ + | +help: use a standard comparison instead + | +LL | let _ = Some(5) == Some(5); + | ~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:19:13 @@ -35,113 +49,243 @@ LL | | }); | help: use is_some_and instead | -LL ~ let _ = Some(5).is_some_and(|n| { -LL + let _ = n; -LL + 6 >= 5 -LL ~ }); +LL - let _ = Some(5).map_or(false, |n| { +LL + let _ = Some(5).is_some_and(|n| { | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:23:13 | LL | let _ = Some(vec![5]).map_or(false, |n| n == [5]); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_some_and instead: `Some(vec![5]).is_some_and(|n| n == [5])` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - let _ = Some(vec![5]).map_or(false, |n| n == [5]); +LL + let _ = Some(vec![5]).is_some_and(|n| n == [5]); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:24:13 | LL | let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_some_and instead: `Some(vec![1]).is_some_and(|n| vec![2] == n)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - let _ = Some(vec![1]).map_or(false, |n| vec![2] == n); +LL + let _ = Some(vec![1]).is_some_and(|n| vec![2] == n); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:25:13 | LL | let _ = Some(5).map_or(false, |n| n == n); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_some_and instead: `Some(5).is_some_and(|n| n == n)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - let _ = Some(5).map_or(false, |n| n == n); +LL + let _ = Some(5).is_some_and(|n| n == n); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:26:13 | LL | let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_some_and instead: `Some(5).is_some_and(|n| n == if 2 > 1 { n } else { 0 })` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - let _ = Some(5).map_or(false, |n| n == if 2 > 1 { n } else { 0 }); +LL + let _ = Some(5).is_some_and(|n| n == if 2 > 1 { n } else { 0 }); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:27:13 | LL | let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_ok_and instead: `Ok::, i32>(vec![5]).is_ok_and(|n| n == [5])` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_ok_and instead + | +LL - let _ = Ok::, i32>(vec![5]).map_or(false, |n| n == [5]); +LL + let _ = Ok::, i32>(vec![5]).is_ok_and(|n| n == [5]); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:28:13 | LL | let _ = Ok::(5).map_or(false, |n| n == 5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `Ok::(5) == Ok(5)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = Ok::(5) == Ok(5); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:29:13 | LL | let _ = Some(5).map_or(false, |n| n == 5).then(|| 1); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `(Some(5) == Some(5))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = (Some(5) == Some(5)).then(|| 1); + | ~~~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:30:13 | LL | let _ = Some(5).map_or(true, |n| n == 5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_none_or instead: `Some(5).is_none_or(|n| n == 5)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - let _ = Some(5).map_or(true, |n| n == 5); +LL + let _ = Some(5).is_none_or(|n| n == 5); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:31:13 | LL | let _ = Some(5).map_or(true, |n| 5 == n); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_none_or instead: `Some(5).is_none_or(|n| 5 == n)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - let _ = Some(5).map_or(true, |n| 5 == n); +LL + let _ = Some(5).is_none_or(|n| 5 == n); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:32:14 | LL | let _ = !Some(5).map_or(false, |n| n == 5); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `(Some(5) == Some(5))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = !(Some(5) == Some(5)); + | ~~~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:33:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) || false; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `(Some(5) == Some(5))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = (Some(5) == Some(5)) || false; + | ~~~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:34:13 | LL | let _ = Some(5).map_or(false, |n| n == 5) as usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `(Some(5) == Some(5))` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = (Some(5) == Some(5)) as usize; + | ~~~~~~~~~~~~~~~~~~~~ error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:58:13 | LL | let _ = r.map_or(false, |x| x == 7); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_ok_and instead: `r.is_ok_and(|x| x == 7)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_ok_and instead + | +LL - let _ = r.map_or(false, |x| x == 7); +LL + let _ = r.is_ok_and(|x| x == 7); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:63:13 | LL | let _ = r.map_or(false, func); - | ^^^^^^^^^^^^^^^^^^^^^ help: use is_ok_and instead: `r.is_ok_and(func)` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_ok_and instead + | +LL - let _ = r.map_or(false, func); +LL + let _ = r.is_ok_and(func); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:64:13 | LL | let _ = Some(5).map_or(false, func); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_some_and instead: `Some(5).is_some_and(func)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_some_and instead + | +LL - let _ = Some(5).map_or(false, func); +LL + let _ = Some(5).is_some_and(func); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:65:13 | LL | let _ = Some(5).map_or(true, func); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use is_none_or instead: `Some(5).is_none_or(func)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - let _ = Some(5).map_or(true, func); +LL + let _ = Some(5).is_none_or(func); + | error: this `map_or` can be simplified --> tests/ui/unnecessary_map_or.rs:70:13 | LL | let _ = r.map_or(false, |x| x == 8); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use a standard comparison instead: `r == Ok(8)` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use a standard comparison instead + | +LL | let _ = r == Ok(8); + | ~~~~~~~~~~ -error: aborting due to 21 previous errors +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:90:5 + | +LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) +LL + o.is_none_or(|n| n > 5) || (o as &Option).map_or(true, |n| n < 5) + | + +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:90:34 + | +LL | o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - o.map_or(true, |n| n > 5) || (o as &Option).map_or(true, |n| n < 5) +LL + o.map_or(true, |n| n > 5) || (o as &Option).is_none_or(|n| n < 5) + | + +error: this `map_or` can be simplified + --> tests/ui/unnecessary_map_or.rs:103:5 + | +LL | o.map_or(true, |n| n > 5) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use is_none_or instead + | +LL - o.map_or(true, |n| n > 5) +LL + o.is_none_or(|n| n > 5) + | + +error: aborting due to 24 previous errors diff --git a/tests/ui/unnecessary_semicolon.edition2021.fixed b/tests/ui/unnecessary_semicolon.edition2021.fixed new file mode 100644 index 000000000000..7a3b79553dea --- /dev/null +++ b/tests/ui/unnecessary_semicolon.edition2021.fixed @@ -0,0 +1,57 @@ +//@revisions: edition2021 edition2024 +//@[edition2021] edition:2021 +//@[edition2024] edition:2024 + +#![warn(clippy::unnecessary_semicolon)] +#![feature(postfix_match)] +#![allow(clippy::single_match)] + +fn no_lint(mut x: u32) -> Option { + Some(())?; + + { + let y = 3; + dbg!(x + y) + }; + + { + let (mut a, mut b) = (10, 20); + (a, b) = (b + 1, a + 1); + } + + Some(0) +} + +fn main() { + let mut a = 3; + if a == 2 { + println!("This is weird"); + } + //~^ ERROR: unnecessary semicolon + + a.match { + 3 => println!("three"), + _ => println!("not three"), + } + //~^ ERROR: unnecessary semicolon +} + +// This is a problem in edition 2021 and below +fn borrow_issue() { + let v = std::cell::RefCell::new(Some(vec![1])); + match &*v.borrow() { + Some(v) => { + dbg!(v); + }, + None => {}, + }; +} + +fn no_borrow_issue(a: u32, b: u32) { + match Some(a + b) { + Some(v) => { + dbg!(v); + }, + None => {}, + } +} diff --git a/tests/ui/unnecessary_semicolon.edition2021.stderr b/tests/ui/unnecessary_semicolon.edition2021.stderr new file mode 100644 index 000000000000..ccff33084172 --- /dev/null +++ b/tests/ui/unnecessary_semicolon.edition2021.stderr @@ -0,0 +1,23 @@ +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:29:6 + | +LL | }; + | ^ help: remove + | + = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]` + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:35:6 + | +LL | }; + | ^ help: remove + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:56:6 + | +LL | }; + | ^ help: remove + +error: aborting due to 3 previous errors + diff --git a/tests/ui/unnecessary_semicolon.edition2024.fixed b/tests/ui/unnecessary_semicolon.edition2024.fixed new file mode 100644 index 000000000000..d186d5e7ebc4 --- /dev/null +++ b/tests/ui/unnecessary_semicolon.edition2024.fixed @@ -0,0 +1,57 @@ +//@revisions: edition2021 edition2024 +//@[edition2021] edition:2021 +//@[edition2024] edition:2024 + +#![warn(clippy::unnecessary_semicolon)] +#![feature(postfix_match)] +#![allow(clippy::single_match)] + +fn no_lint(mut x: u32) -> Option { + Some(())?; + + { + let y = 3; + dbg!(x + y) + }; + + { + let (mut a, mut b) = (10, 20); + (a, b) = (b + 1, a + 1); + } + + Some(0) +} + +fn main() { + let mut a = 3; + if a == 2 { + println!("This is weird"); + } + //~^ ERROR: unnecessary semicolon + + a.match { + 3 => println!("three"), + _ => println!("not three"), + } + //~^ ERROR: unnecessary semicolon +} + +// This is a problem in edition 2021 and below +fn borrow_issue() { + let v = std::cell::RefCell::new(Some(vec![1])); + match &*v.borrow() { + Some(v) => { + dbg!(v); + }, + None => {}, + } +} + +fn no_borrow_issue(a: u32, b: u32) { + match Some(a + b) { + Some(v) => { + dbg!(v); + }, + None => {}, + } +} diff --git a/tests/ui/unnecessary_semicolon.edition2024.stderr b/tests/ui/unnecessary_semicolon.edition2024.stderr new file mode 100644 index 000000000000..4e526af2147d --- /dev/null +++ b/tests/ui/unnecessary_semicolon.edition2024.stderr @@ -0,0 +1,29 @@ +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:29:6 + | +LL | }; + | ^ help: remove + | + = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]` + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:35:6 + | +LL | }; + | ^ help: remove + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:47:6 + | +LL | }; + | ^ help: remove + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:56:6 + | +LL | }; + | ^ help: remove + +error: aborting due to 4 previous errors + diff --git a/tests/ui/unnecessary_semicolon.fixed b/tests/ui/unnecessary_semicolon.fixed new file mode 100644 index 000000000000..36d5c7806fe8 --- /dev/null +++ b/tests/ui/unnecessary_semicolon.fixed @@ -0,0 +1,32 @@ +#![warn(clippy::unnecessary_semicolon)] +#![feature(postfix_match)] + +fn no_lint(mut x: u32) -> Option { + Some(())?; + + { + let y = 3; + dbg!(x + y) + }; + + { + let (mut a, mut b) = (10, 20); + (a, b) = (b + 1, a + 1); + } + + Some(0) +} + +fn main() { + let mut a = 3; + if a == 2 { + println!("This is weird"); + } + //~^ ERROR: unnecessary semicolon + + a.match { + 3 => println!("three"), + _ => println!("not three"), + } + //~^ ERROR: unnecessary semicolon +} diff --git a/tests/ui/unnecessary_semicolon.rs b/tests/ui/unnecessary_semicolon.rs new file mode 100644 index 000000000000..3028c5b27b34 --- /dev/null +++ b/tests/ui/unnecessary_semicolon.rs @@ -0,0 +1,57 @@ +//@revisions: edition2021 edition2024 +//@[edition2021] edition:2021 +//@[edition2024] edition:2024 + +#![warn(clippy::unnecessary_semicolon)] +#![feature(postfix_match)] +#![allow(clippy::single_match)] + +fn no_lint(mut x: u32) -> Option { + Some(())?; + + { + let y = 3; + dbg!(x + y) + }; + + { + let (mut a, mut b) = (10, 20); + (a, b) = (b + 1, a + 1); + } + + Some(0) +} + +fn main() { + let mut a = 3; + if a == 2 { + println!("This is weird"); + }; + //~^ ERROR: unnecessary semicolon + + a.match { + 3 => println!("three"), + _ => println!("not three"), + }; + //~^ ERROR: unnecessary semicolon +} + +// This is a problem in edition 2021 and below +fn borrow_issue() { + let v = std::cell::RefCell::new(Some(vec![1])); + match &*v.borrow() { + Some(v) => { + dbg!(v); + }, + None => {}, + }; +} + +fn no_borrow_issue(a: u32, b: u32) { + match Some(a + b) { + Some(v) => { + dbg!(v); + }, + None => {}, + }; +} diff --git a/tests/ui/unnecessary_semicolon.stderr b/tests/ui/unnecessary_semicolon.stderr new file mode 100644 index 000000000000..e6bf36e81e88 --- /dev/null +++ b/tests/ui/unnecessary_semicolon.stderr @@ -0,0 +1,17 @@ +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:24:6 + | +LL | }; + | ^ help: remove + | + = note: `-D clippy::unnecessary-semicolon` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_semicolon)]` + +error: unnecessary semicolon + --> tests/ui/unnecessary_semicolon.rs:30:6 + | +LL | }; + | ^ help: remove + +error: aborting due to 2 previous errors + diff --git a/tests/ui/unnecessary_to_owned.fixed b/tests/ui/unnecessary_to_owned.fixed index fdcac8fb08dc..027dac419375 100644 --- a/tests/ui/unnecessary_to_owned.fixed +++ b/tests/ui/unnecessary_to_owned.fixed @@ -585,3 +585,9 @@ fn borrow_checks() { HashSet::::new().foo::<&str>(&"".to_owned()); HashSet::::new().get(&1.to_string()); } + +fn issue13624() -> impl IntoIterator { + let cow: Cow<'_, Vec> = Cow::Owned(vec![String::from("foo")]); + + cow.into_owned().into_iter() +} diff --git a/tests/ui/unnecessary_to_owned.rs b/tests/ui/unnecessary_to_owned.rs index 10a9727a9a79..b89f3d552f84 100644 --- a/tests/ui/unnecessary_to_owned.rs +++ b/tests/ui/unnecessary_to_owned.rs @@ -585,3 +585,9 @@ fn borrow_checks() { HashSet::::new().foo::<&str>(&"".to_owned()); HashSet::::new().get(&1.to_string()); } + +fn issue13624() -> impl IntoIterator { + let cow: Cow<'_, Vec> = Cow::Owned(vec![String::from("foo")]); + + cow.into_owned().into_iter() +} diff --git a/tests/ui/unneeded_struct_pattern.fixed b/tests/ui/unneeded_struct_pattern.fixed new file mode 100644 index 000000000000..5bd269896a6b --- /dev/null +++ b/tests/ui/unneeded_struct_pattern.fixed @@ -0,0 +1,177 @@ +//@aux-build:non-exhaustive-enum.rs +#![allow( + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or, + clippy::redundant_pattern_matching +)] +#![warn(clippy::unneeded_struct_pattern)] + +extern crate non_exhaustive_enum; +use non_exhaustive_enum::*; + +fn noop() {} + +fn main() { + match Some(114514) { + Some(v) => v, + None => 0, + }; + + match Some(1919810) { + Some(v) => v, + None => 0, + }; + + match Some(123456) { + Some(v) => v, + None => 0, + }; + + match Some(Some(123456)) { + Some(Some(v)) => v, + Some(None) => 0, + None => 0, + }; + + if let None = Some(0) {} + if let None = Some(0) {} + if let Some(None) = Some(Some(0)) {} + let None = Some(0) else { panic!() }; + let None = Some(0) else { panic!() }; + let Some(None) = Some(Some(0)) else { panic!() }; + + enum Custom { + HasFields { + field: i32, + }, + HasBracketsNoFields {}, + NoBrackets, + #[non_exhaustive] + NoBracketsNonExhaustive, + Init, + }; + + match Custom::Init { + Custom::HasFields { field: value } => value, + Custom::HasBracketsNoFields {} => 0, + Custom::NoBrackets => 0, //~ ERROR: struct pattern is not needed for a unit variant + Custom::NoBracketsNonExhaustive => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::HasFields { field: value } => value, + Custom::HasBracketsNoFields { .. } => 0, + Custom::NoBrackets => 0, //~ ERROR: struct pattern is not needed for a unit variant + Custom::NoBracketsNonExhaustive => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::NoBrackets if true => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::NoBrackets | Custom::NoBracketsNonExhaustive => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + if let Custom::HasFields { field: value } = Custom::Init { + noop(); + } + if let Custom::HasBracketsNoFields {} = Custom::Init { + noop(); + } + if let Custom::HasBracketsNoFields { .. } = Custom::Init { + noop(); + } + if let Custom::NoBrackets = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBrackets = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBrackets | Custom::NoBracketsNonExhaustive = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBracketsNonExhaustive = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBracketsNonExhaustive = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + + let Custom::HasFields { field: value } = Custom::Init else { + panic!() + }; + + let Custom::HasBracketsNoFields {} = Custom::Init else { + panic!() + }; + + let Custom::HasBracketsNoFields { .. } = Custom::Init else { + panic!() + }; + let Custom::NoBrackets = Custom::Init else { panic!() }; //~ ERROR: struct pattern is not needed for a unit variant + + let Custom::NoBrackets = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + let Custom::NoBracketsNonExhaustive = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + let Custom::NoBracketsNonExhaustive = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + + enum Refutable { + Variant, + } + + fn pat_in_fn_param_1(Refutable::Variant: Refutable) {} //~ ERROR: struct pattern is not needed for a unit variant + fn pat_in_fn_param_2(Refutable::Variant: Refutable) {} //~ ERROR: struct pattern is not needed for a unit variant + + for Refutable::Variant in [] {} //~ ERROR: struct pattern is not needed for a unit variant + for Refutable::Variant in [] {} //~ ERROR: struct pattern is not needed for a unit variant +} + +fn external_crate() { + use ExtNonExhaustiveVariant::*; + + match ExhaustiveUnit { + // Expected + ExhaustiveUnit => 0, + _ => 0, + }; + + match ExhaustiveUnit { + // Exhaustive variant + ExhaustiveUnit => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match ExhaustiveUnit { + // Exhaustive variant + ExhaustiveUnit => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match ExhaustiveUnit { + ExhaustiveUnit => 0, + // vvvvv Non-exhaustive variants, should all be ignored + Unit { .. } => 0, + Tuple { 0: field, .. } => field, + StructNoField { .. } => 0, + Struct { field, .. } => field, + _ => 0, + }; +} diff --git a/tests/ui/unneeded_struct_pattern.rs b/tests/ui/unneeded_struct_pattern.rs new file mode 100644 index 000000000000..c7658617ad37 --- /dev/null +++ b/tests/ui/unneeded_struct_pattern.rs @@ -0,0 +1,177 @@ +//@aux-build:non-exhaustive-enum.rs +#![allow( + clippy::manual_unwrap_or_default, + clippy::manual_unwrap_or, + clippy::redundant_pattern_matching +)] +#![warn(clippy::unneeded_struct_pattern)] + +extern crate non_exhaustive_enum; +use non_exhaustive_enum::*; + +fn noop() {} + +fn main() { + match Some(114514) { + Some(v) => v, + None {} => 0, + }; + + match Some(1919810) { + Some(v) => v, + None { .. } => 0, + }; + + match Some(123456) { + Some(v) => v, + None => 0, + }; + + match Some(Some(123456)) { + Some(Some(v)) => v, + Some(None {}) => 0, + None {} => 0, + }; + + if let None {} = Some(0) {} + if let None { .. } = Some(0) {} + if let Some(None {}) = Some(Some(0)) {} + let None {} = Some(0) else { panic!() }; + let None { .. } = Some(0) else { panic!() }; + let Some(None {}) = Some(Some(0)) else { panic!() }; + + enum Custom { + HasFields { + field: i32, + }, + HasBracketsNoFields {}, + NoBrackets, + #[non_exhaustive] + NoBracketsNonExhaustive, + Init, + }; + + match Custom::Init { + Custom::HasFields { field: value } => value, + Custom::HasBracketsNoFields {} => 0, + Custom::NoBrackets {} => 0, //~ ERROR: struct pattern is not needed for a unit variant + Custom::NoBracketsNonExhaustive {} => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::HasFields { field: value } => value, + Custom::HasBracketsNoFields { .. } => 0, + Custom::NoBrackets { .. } => 0, //~ ERROR: struct pattern is not needed for a unit variant + Custom::NoBracketsNonExhaustive { .. } => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::NoBrackets {} if true => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match Custom::Init { + Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + if let Custom::HasFields { field: value } = Custom::Init { + noop(); + } + if let Custom::HasBracketsNoFields {} = Custom::Init { + noop(); + } + if let Custom::HasBracketsNoFields { .. } = Custom::Init { + noop(); + } + if let Custom::NoBrackets {} = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBrackets { .. } = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBracketsNonExhaustive {} = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + if let Custom::NoBracketsNonExhaustive { .. } = Custom::Init { + //~^ ERROR: struct pattern is not needed for a unit variant + noop(); + } + + let Custom::HasFields { field: value } = Custom::Init else { + panic!() + }; + + let Custom::HasBracketsNoFields {} = Custom::Init else { + panic!() + }; + + let Custom::HasBracketsNoFields { .. } = Custom::Init else { + panic!() + }; + let Custom::NoBrackets {} = Custom::Init else { panic!() }; //~ ERROR: struct pattern is not needed for a unit variant + + let Custom::NoBrackets { .. } = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + let Custom::NoBracketsNonExhaustive {} = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + let Custom::NoBracketsNonExhaustive { .. } = Custom::Init else { + //~^ ERROR: struct pattern is not needed for a unit variant + panic!() + }; + + enum Refutable { + Variant, + } + + fn pat_in_fn_param_1(Refutable::Variant {}: Refutable) {} //~ ERROR: struct pattern is not needed for a unit variant + fn pat_in_fn_param_2(Refutable::Variant { .. }: Refutable) {} //~ ERROR: struct pattern is not needed for a unit variant + + for Refutable::Variant {} in [] {} //~ ERROR: struct pattern is not needed for a unit variant + for Refutable::Variant { .. } in [] {} //~ ERROR: struct pattern is not needed for a unit variant +} + +fn external_crate() { + use ExtNonExhaustiveVariant::*; + + match ExhaustiveUnit { + // Expected + ExhaustiveUnit => 0, + _ => 0, + }; + + match ExhaustiveUnit { + // Exhaustive variant + ExhaustiveUnit { .. } => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match ExhaustiveUnit { + // Exhaustive variant + ExhaustiveUnit {} => 0, //~ ERROR: struct pattern is not needed for a unit variant + _ => 0, + }; + + match ExhaustiveUnit { + ExhaustiveUnit => 0, + // vvvvv Non-exhaustive variants, should all be ignored + Unit { .. } => 0, + Tuple { 0: field, .. } => field, + StructNoField { .. } => 0, + Struct { field, .. } => field, + _ => 0, + }; +} diff --git a/tests/ui/unneeded_struct_pattern.stderr b/tests/ui/unneeded_struct_pattern.stderr new file mode 100644 index 000000000000..3a7f59583802 --- /dev/null +++ b/tests/ui/unneeded_struct_pattern.stderr @@ -0,0 +1,203 @@ +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:17:13 + | +LL | None {} => 0, + | ^^^ help: remove the struct pattern + | + = note: `-D clippy::unneeded-struct-pattern` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unneeded_struct_pattern)]` + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:22:13 + | +LL | None { .. } => 0, + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:32:18 + | +LL | Some(None {}) => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:33:13 + | +LL | None {} => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:36:16 + | +LL | if let None {} = Some(0) {} + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:37:16 + | +LL | if let None { .. } = Some(0) {} + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:38:21 + | +LL | if let Some(None {}) = Some(Some(0)) {} + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:39:13 + | +LL | let None {} = Some(0) else { panic!() }; + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:40:13 + | +LL | let None { .. } = Some(0) else { panic!() }; + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:41:18 + | +LL | let Some(None {}) = Some(Some(0)) else { panic!() }; + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:57:27 + | +LL | Custom::NoBrackets {} => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:58:40 + | +LL | Custom::NoBracketsNonExhaustive {} => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:65:27 + | +LL | Custom::NoBrackets { .. } => 0, + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:66:40 + | +LL | Custom::NoBracketsNonExhaustive { .. } => 0, + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:71:27 + | +LL | Custom::NoBrackets {} if true => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:76:27 + | +LL | Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:76:64 + | +LL | Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} => 0, + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:89:30 + | +LL | if let Custom::NoBrackets {} = Custom::Init { + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:93:30 + | +LL | if let Custom::NoBrackets { .. } = Custom::Init { + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:97:30 + | +LL | if let Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} = Custom::Init { + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:97:67 + | +LL | if let Custom::NoBrackets {} | Custom::NoBracketsNonExhaustive {} = Custom::Init { + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:101:43 + | +LL | if let Custom::NoBracketsNonExhaustive {} = Custom::Init { + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:105:43 + | +LL | if let Custom::NoBracketsNonExhaustive { .. } = Custom::Init { + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:121:27 + | +LL | let Custom::NoBrackets {} = Custom::Init else { panic!() }; + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:123:27 + | +LL | let Custom::NoBrackets { .. } = Custom::Init else { + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:127:40 + | +LL | let Custom::NoBracketsNonExhaustive {} = Custom::Init else { + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:131:40 + | +LL | let Custom::NoBracketsNonExhaustive { .. } = Custom::Init else { + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:140:44 + | +LL | fn pat_in_fn_param_1(Refutable::Variant {}: Refutable) {} + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:141:44 + | +LL | fn pat_in_fn_param_2(Refutable::Variant { .. }: Refutable) {} + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:143:27 + | +LL | for Refutable::Variant {} in [] {} + | ^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:144:27 + | +LL | for Refutable::Variant { .. } in [] {} + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:158:23 + | +LL | ExhaustiveUnit { .. } => 0, + | ^^^^^^^ help: remove the struct pattern + +error: struct pattern is not needed for a unit variant + --> tests/ui/unneeded_struct_pattern.rs:164:23 + | +LL | ExhaustiveUnit {} => 0, + | ^^^ help: remove the struct pattern + +error: aborting due to 33 previous errors + diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index 2f7edd92bb7c..697d437b3885 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -342,3 +342,56 @@ fn gen_identity(x: [T; 3]) -> Vec { x.into_iter().collect() //~^ useless_conversion } + +mod issue11819 { + fn takes_into_iter(_: impl IntoIterator) {} + + pub struct MyStruct { + my_field: T, + } + + impl MyStruct { + pub fn with_ref<'a>(&'a mut self) + where + &'a T: IntoIterator, + { + takes_into_iter(&self.my_field); + //~^ useless_conversion + } + + pub fn with_ref_mut<'a>(&'a mut self) + where + &'a mut T: IntoIterator, + { + takes_into_iter(&mut self.my_field); + //~^ useless_conversion + } + + pub fn with_deref(&mut self) + where + T: std::ops::Deref, + Y: IntoIterator + Copy, + { + takes_into_iter(*self.my_field); + //~^ useless_conversion + } + + pub fn with_reborrow<'a, Y: 'a>(&'a mut self) + where + T: std::ops::Deref, + &'a Y: IntoIterator, + { + takes_into_iter(&*self.my_field); + //~^ useless_conversion + } + + pub fn with_reborrow_mut<'a, Y: 'a>(&'a mut self) + where + T: std::ops::Deref + std::ops::DerefMut, + &'a mut Y: IntoIterator, + { + takes_into_iter(&mut *self.my_field); + //~^ useless_conversion + } + } +} diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index eacdf77f9052..4d8ad61a8c99 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -342,3 +342,56 @@ fn gen_identity(x: [T; 3]) -> Vec { x.into_iter().map(Into::into).collect() //~^ useless_conversion } + +mod issue11819 { + fn takes_into_iter(_: impl IntoIterator) {} + + pub struct MyStruct { + my_field: T, + } + + impl MyStruct { + pub fn with_ref<'a>(&'a mut self) + where + &'a T: IntoIterator, + { + takes_into_iter(self.my_field.into_iter()); + //~^ useless_conversion + } + + pub fn with_ref_mut<'a>(&'a mut self) + where + &'a mut T: IntoIterator, + { + takes_into_iter(self.my_field.into_iter()); + //~^ useless_conversion + } + + pub fn with_deref(&mut self) + where + T: std::ops::Deref, + Y: IntoIterator + Copy, + { + takes_into_iter(self.my_field.into_iter()); + //~^ useless_conversion + } + + pub fn with_reborrow<'a, Y: 'a>(&'a mut self) + where + T: std::ops::Deref, + &'a Y: IntoIterator, + { + takes_into_iter(self.my_field.into_iter()); + //~^ useless_conversion + } + + pub fn with_reborrow_mut<'a, Y: 'a>(&'a mut self) + where + T: std::ops::Deref + std::ops::DerefMut, + &'a mut Y: IntoIterator, + { + takes_into_iter(self.my_field.into_iter()); + //~^ useless_conversion + } + } +} diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 6aeb382902ba..ed50f3071862 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -122,7 +122,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:189:7 | LL | b(vec![1, 2].into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]` + | ^^^^^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:179:13 @@ -134,7 +136,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:190:7 | LL | c(vec![1, 2].into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]` + | ^^^^^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:180:18 @@ -146,7 +150,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:191:7 | LL | d(vec![1, 2].into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `vec![1, 2]` + | ^^^^^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:183:12 @@ -158,7 +164,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:194:7 | LL | b(vec![1, 2].into_iter().into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`s: `vec![1, 2]` + | ^^^^^^^^^^------------------------ + | | + | help: consider removing the `.into_iter()`s | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:179:13 @@ -170,7 +178,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:195:7 | LL | b(vec![1, 2].into_iter().into_iter().into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`s: `vec![1, 2]` + | ^^^^^^^^^^------------------------------------ + | | + | help: consider removing the `.into_iter()`s | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:179:13 @@ -182,7 +192,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:241:24 | LL | foo2::([1, 2, 3].into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `[1, 2, 3]` + | ^^^^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:220:12 @@ -194,7 +206,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:249:14 | LL | foo3([1, 2, 3].into_iter()); - | ^^^^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `[1, 2, 3]` + | ^^^^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:229:12 @@ -206,7 +220,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:258:16 | LL | S1.foo([1, 2].into_iter()); - | ^^^^^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `[1, 2]` + | ^^^^^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:255:27 @@ -218,7 +234,9 @@ error: explicit call to `.into_iter()` in function argument accepting `IntoItera --> tests/ui/useless_conversion.rs:277:44 | LL | v0.into_iter().interleave_shortest(v1.into_iter()); - | ^^^^^^^^^^^^^^ help: consider removing the `.into_iter()`: `v1` + | ^^------------ + | | + | help: consider removing the `.into_iter()` | note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` --> tests/ui/useless_conversion.rs:264:20 @@ -274,5 +292,90 @@ error: useless conversion to the same type: `T` LL | x.into_iter().map(Into::into).collect() | ^^^^^^^^^^^^^^^^ help: consider removing -error: aborting due to 36 previous errors +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:358:29 + | +LL | takes_into_iter(self.my_field.into_iter()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:347:32 + | +LL | fn takes_into_iter(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - takes_into_iter(self.my_field.into_iter()); +LL + takes_into_iter(&self.my_field); + | + +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:366:29 + | +LL | takes_into_iter(self.my_field.into_iter()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:347:32 + | +LL | fn takes_into_iter(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - takes_into_iter(self.my_field.into_iter()); +LL + takes_into_iter(&mut self.my_field); + | + +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:375:29 + | +LL | takes_into_iter(self.my_field.into_iter()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:347:32 + | +LL | fn takes_into_iter(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - takes_into_iter(self.my_field.into_iter()); +LL + takes_into_iter(*self.my_field); + | + +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:384:29 + | +LL | takes_into_iter(self.my_field.into_iter()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:347:32 + | +LL | fn takes_into_iter(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - takes_into_iter(self.my_field.into_iter()); +LL + takes_into_iter(&*self.my_field); + | + +error: explicit call to `.into_iter()` in function argument accepting `IntoIterator` + --> tests/ui/useless_conversion.rs:393:29 + | +LL | takes_into_iter(self.my_field.into_iter()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: this parameter accepts any `IntoIterator`, so you don't need to call `.into_iter()` + --> tests/ui/useless_conversion.rs:347:32 + | +LL | fn takes_into_iter(_: impl IntoIterator) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider removing the `.into_iter()` + | +LL - takes_into_iter(self.my_field.into_iter()); +LL + takes_into_iter(&mut *self.my_field); + | + +error: aborting due to 41 previous errors diff --git a/tests/ui/useless_nonzero_new_unchecked.fixed b/tests/ui/useless_nonzero_new_unchecked.fixed new file mode 100644 index 000000000000..03b34afa54ec --- /dev/null +++ b/tests/ui/useless_nonzero_new_unchecked.fixed @@ -0,0 +1,52 @@ +#![warn(clippy::useless_nonzero_new_unchecked)] + +use std::num::{NonZero, NonZeroUsize}; + +#[clippy::msrv = "1.83"] +const fn func() -> NonZeroUsize { + const { NonZeroUsize::new(3).unwrap() } + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context +} + +#[clippy::msrv = "1.82"] +const fn func_older() -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(3) } +} + +const fn func_performance_hit_if_linted() -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(3) } +} + +const fn func_may_panic_at_run_time_if_linted(x: usize) -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(x) } +} + +macro_rules! uns { + ($expr:expr) => { + unsafe { $expr } + }; +} + +macro_rules! nzu { + () => { + NonZeroUsize::new_unchecked(1) + }; +} + +fn main() { + const _A: NonZeroUsize = NonZeroUsize::new(3).unwrap(); + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + static _B: NonZero = NonZero::::new(42).unwrap(); + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const _C: usize = unsafe { NonZeroUsize::new(3).unwrap().get() }; + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const AUX: usize = 3; + const _D: NonZeroUsize = NonZeroUsize::new(AUX).unwrap(); + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const _X: NonZeroUsize = uns!(NonZeroUsize::new_unchecked(3)); + const _Y: NonZeroUsize = unsafe { nzu!() }; +} diff --git a/tests/ui/useless_nonzero_new_unchecked.rs b/tests/ui/useless_nonzero_new_unchecked.rs new file mode 100644 index 000000000000..d450e3a03ec5 --- /dev/null +++ b/tests/ui/useless_nonzero_new_unchecked.rs @@ -0,0 +1,52 @@ +#![warn(clippy::useless_nonzero_new_unchecked)] + +use std::num::{NonZero, NonZeroUsize}; + +#[clippy::msrv = "1.83"] +const fn func() -> NonZeroUsize { + const { unsafe { NonZeroUsize::new_unchecked(3) } } + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context +} + +#[clippy::msrv = "1.82"] +const fn func_older() -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(3) } +} + +const fn func_performance_hit_if_linted() -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(3) } +} + +const fn func_may_panic_at_run_time_if_linted(x: usize) -> NonZeroUsize { + unsafe { NonZeroUsize::new_unchecked(x) } +} + +macro_rules! uns { + ($expr:expr) => { + unsafe { $expr } + }; +} + +macro_rules! nzu { + () => { + NonZeroUsize::new_unchecked(1) + }; +} + +fn main() { + const _A: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + static _B: NonZero = unsafe { NonZero::::new_unchecked(42) }; + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const _C: usize = unsafe { NonZeroUsize::new_unchecked(3).get() }; + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const AUX: usize = 3; + const _D: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(AUX) }; + //~^ ERROR: `Option::unwrap()` can be safely used in a `const` context + + const _X: NonZeroUsize = uns!(NonZeroUsize::new_unchecked(3)); + const _Y: NonZeroUsize = unsafe { nzu!() }; +} diff --git a/tests/ui/useless_nonzero_new_unchecked.stderr b/tests/ui/useless_nonzero_new_unchecked.stderr new file mode 100644 index 000000000000..adb146167633 --- /dev/null +++ b/tests/ui/useless_nonzero_new_unchecked.stderr @@ -0,0 +1,37 @@ +error: `NonZeroUsize::new()` and `Option::unwrap()` can be safely used in a `const` context + --> tests/ui/useless_nonzero_new_unchecked.rs:7:13 + | +LL | const { unsafe { NonZeroUsize::new_unchecked(3) } } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use instead: `NonZeroUsize::new(3).unwrap()` + | + = note: `-D clippy::useless-nonzero-new-unchecked` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::useless_nonzero_new_unchecked)]` + +error: `NonZeroUsize::new()` and `Option::unwrap()` can be safely used in a `const` context + --> tests/ui/useless_nonzero_new_unchecked.rs:37:30 + | +LL | const _A: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(3) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use instead: `NonZeroUsize::new(3).unwrap()` + +error: `NonZero::::new()` and `Option::unwrap()` can be safely used in a `const` context + --> tests/ui/useless_nonzero_new_unchecked.rs:40:30 + | +LL | static _B: NonZero = unsafe { NonZero::::new_unchecked(42) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use instead: `NonZero::::new(42).unwrap()` + +error: `NonZeroUsize::new()` and `Option::unwrap()` can be safely used in a `const` context + --> tests/ui/useless_nonzero_new_unchecked.rs:43:32 + | +LL | const _C: usize = unsafe { NonZeroUsize::new_unchecked(3).get() }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use instead: `NonZeroUsize::new(3).unwrap()` + | + = note: the fixed expression does not require an `unsafe` context + +error: `NonZeroUsize::new()` and `Option::unwrap()` can be safely used in a `const` context + --> tests/ui/useless_nonzero_new_unchecked.rs:47:30 + | +LL | const _D: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(AUX) }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use instead: `NonZeroUsize::new(AUX).unwrap()` + +error: aborting due to 5 previous errors + diff --git a/tests/versioncheck.rs b/tests/versioncheck.rs index e29898f068d3..ed357137095b 100644 --- a/tests/versioncheck.rs +++ b/tests/versioncheck.rs @@ -88,5 +88,5 @@ fn check_that_clippy_has_the_same_major_version_as_rustc() { _ => { panic!("Failed to parse rustc version: {vsplit:?}"); }, - }; + } } diff --git a/triagebot.toml b/triagebot.toml index eadfd7107c77..3d35116ebc17 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -29,7 +29,6 @@ users_on_vacation = [ "*" = [ "@Manishearth", "@llogiq", - "@xFrednet", "@Alexendoo", "@dswij", "@Jarcho", diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index c2197b89c566..34d76ad642ec 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -341,8 +341,8 @@ window.filters = { || !filters.levels_filter[lint.level] || !filters.applicabilities_filter[lint.applicability] || !(filters.version_filter["="] === null || lint.version === filters.version_filter["="]) - || !(filters.version_filter["≥"] === null || lint.version > filters.version_filter["≥"]) - || !(filters.version_filter["≤"] === null || lint.version < filters.version_filter["≤"]) + || !(filters.version_filter["≥"] === null || lint.version >= filters.version_filter["≥"]) + || !(filters.version_filter["≤"] === null || lint.version <= filters.version_filter["≤"]) ); if (lint.filteredOut || lint.searchFilteredOut) { lint.elem.style.display = "none"; From abb3e8e7810af9836257e8f58bb113d861beb633 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 22 Jan 2025 21:07:54 +0000 Subject: [PATCH 15/57] Make item self/non-self bound naming less whack --- clippy_lints/src/future_not_send.rs | 2 +- clippy_utils/src/ty/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/future_not_send.rs b/clippy_lints/src/future_not_send.rs index bb2dc9995df7..3ccfa51ab70b 100644 --- a/clippy_lints/src/future_not_send.rs +++ b/clippy_lints/src/future_not_send.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend { && let Some(future_trait) = cx.tcx.lang_items().future_trait() && let Some(send_trait) = cx.tcx.get_diagnostic_item(sym::Send) { - let preds = cx.tcx.explicit_item_super_predicates(def_id); + let preds = cx.tcx.explicit_item_self_bounds(def_id); let is_future = preds.iter_instantiated_copied(cx.tcx, args).any(|(p, _)| { p.as_trait_clause() .is_some_and(|trait_pred| trait_pred.skip_binder().trait_ref.def_id == future_trait) diff --git a/clippy_utils/src/ty/mod.rs b/clippy_utils/src/ty/mod.rs index 32e7c2bbf7cb..607d76201028 100644 --- a/clippy_utils/src/ty/mod.rs +++ b/clippy_utils/src/ty/mod.rs @@ -96,7 +96,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' return false; } - for (predicate, _span) in cx.tcx.explicit_item_super_predicates(def_id).iter_identity_copied() { + for (predicate, _span) in cx.tcx.explicit_item_self_bounds(def_id).iter_identity_copied() { match predicate.kind().skip_binder() { // For `impl Trait`, it will register a predicate of `T: Trait`, so we go through // and check substitutions to find `U`. @@ -322,7 +322,7 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { }, ty::Tuple(args) => args.iter().any(|ty| is_must_use_ty(cx, ty)), ty::Alias(ty::Opaque, AliasTy { def_id, .. }) => { - for (predicate, _) in cx.tcx.explicit_item_super_predicates(def_id).skip_binder() { + for (predicate, _) in cx.tcx.explicit_item_self_bounds(def_id).skip_binder() { if let ty::ClauseKind::Trait(trait_predicate) = predicate.kind().skip_binder() { if cx.tcx.has_attr(trait_predicate.trait_ref.def_id, sym::must_use) { return true; @@ -712,7 +712,7 @@ pub fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option sig_from_bounds( cx, ty, - cx.tcx.item_super_predicates(def_id).iter_instantiated(cx.tcx, args), + cx.tcx.item_self_bounds(def_id).iter_instantiated(cx.tcx, args), cx.tcx.opt_parent(def_id), ), ty::FnPtr(sig_tys, hdr) => Some(ExprFnSig::Sig(sig_tys.with(hdr), None)), From d7874f436a3e25d32ed612af5429852152932f10 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 27 Jan 2025 16:30:02 -0800 Subject: [PATCH 16/57] Refactor FnKind variant to hold &Fn --- clippy_lints/src/multiple_bound_locations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/multiple_bound_locations.rs b/clippy_lints/src/multiple_bound_locations.rs index 882ab2dda7aa..0e1980a6acb6 100644 --- a/clippy_lints/src/multiple_bound_locations.rs +++ b/clippy_lints/src/multiple_bound_locations.rs @@ -1,5 +1,5 @@ use rustc_ast::visit::FnKind; -use rustc_ast::{NodeId, WherePredicateKind}; +use rustc_ast::{Fn, NodeId, WherePredicateKind}; use rustc_data_structures::fx::FxHashMap; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; @@ -39,7 +39,7 @@ declare_lint_pass!(MultipleBoundLocations => [MULTIPLE_BOUND_LOCATIONS]); impl EarlyLintPass for MultipleBoundLocations { fn check_fn(&mut self, cx: &EarlyContext<'_>, kind: FnKind<'_>, _: Span, _: NodeId) { - if let FnKind::Fn(_, _, _, _, generics, _) = kind + if let FnKind::Fn(_, _, _, Fn { generics, .. }) = kind && !generics.params.is_empty() && !generics.where_clause.predicates.is_empty() { From 0baa1007100fd72ec6c2b5760e3df74bd4da3fa1 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:14:44 +0000 Subject: [PATCH 17/57] rustc_hir: replace `is_empty()`+indexing with `first()` --- compiler/rustc_hir/src/hir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index a396d705cbb6..3fea6cdbcf72 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -206,7 +206,7 @@ pub type UsePath<'hir> = Path<'hir, SmallVec<[Res; 3]>>; impl Path<'_> { pub fn is_global(&self) -> bool { - !self.segments.is_empty() && self.segments[0].ident.name == kw::PathRoot + self.segments.first().is_some_and(|segment| segment.ident.name == kw::PathRoot) } } From 380222588442bd97bb2887d90337d235627bd073 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:22:26 +0000 Subject: [PATCH 18/57] rustc_hir: use box patterns to flatten some nested pattern matches --- compiler/rustc_hir/src/hir.rs | 33 +++++++++------------------------ compiler/rustc_hir/src/lib.rs | 1 + 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 3fea6cdbcf72..5c8e81a14141 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1061,10 +1061,7 @@ impl Attribute { pub fn value_lit(&self) -> Option<&MetaItemLit> { match &self.kind { - AttrKind::Normal(n) => match n.as_ref() { - AttrItem { args: AttrArgs::Eq { expr, .. }, .. } => Some(expr), - _ => None, - }, + AttrKind::Normal(box AttrItem { args: AttrArgs::Eq { expr, .. }, .. }) => Some(expr), _ => None, } } @@ -1077,12 +1074,9 @@ impl AttributeExt for Attribute { fn meta_item_list(&self) -> Option> { match &self.kind { - AttrKind::Normal(n) => match n.as_ref() { - AttrItem { args: AttrArgs::Delimited(d), .. } => { - ast::MetaItemKind::list_from_tokens(d.tokens.clone()) - } - _ => None, - }, + AttrKind::Normal(box AttrItem { args: AttrArgs::Delimited(d), .. }) => { + ast::MetaItemKind::list_from_tokens(d.tokens.clone()) + } _ => None, } } @@ -1098,14 +1092,10 @@ impl AttributeExt for Attribute { /// For a single-segment attribute, returns its name; otherwise, returns `None`. fn ident(&self) -> Option { match &self.kind { - AttrKind::Normal(n) => { - if let [ident] = n.path.segments.as_ref() { - Some(*ident) - } else { - None - } - } - AttrKind::DocComment(..) => None, + AttrKind::Normal(box AttrItem { + path: AttrPath { segments: box [ident], .. }, .. + }) => Some(*ident), + _ => None, } } @@ -1128,12 +1118,7 @@ impl AttributeExt for Attribute { } fn is_word(&self) -> bool { - match &self.kind { - AttrKind::Normal(n) => { - matches!(n.args, AttrArgs::Empty) - } - AttrKind::DocComment(..) => false, - } + matches!(self.kind, AttrKind::Normal(box AttrItem { args: AttrArgs::Empty, .. })) } fn ident_path(&self) -> Option> { diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs index 705c167e258c..270d4fbec30f 100644 --- a/compiler/rustc_hir/src/lib.rs +++ b/compiler/rustc_hir/src/lib.rs @@ -5,6 +5,7 @@ // tidy-alphabetical-start #![allow(internal_features)] #![feature(associated_type_defaults)] +#![feature(box_patterns)] #![feature(closure_track_caller)] #![feature(debug_closure_helpers)] #![feature(exhaustive_patterns)] From 1d4a419a6ba5f293d054eae64eca6a1a19b76756 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:27:09 +0000 Subject: [PATCH 19/57] rustc_hir: don't open-code `Iterator::eq` --- compiler/rustc_hir/src/hir.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 5c8e81a14141..5745f81db796 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1101,10 +1101,7 @@ impl AttributeExt for Attribute { fn path_matches(&self, name: &[Symbol]) -> bool { match &self.kind { - AttrKind::Normal(n) => { - n.path.segments.len() == name.len() - && n.path.segments.iter().zip(name).all(|(s, n)| s.name == *n) - } + AttrKind::Normal(n) => n.path.segments.iter().map(|segment| &segment.name).eq(name), AttrKind::DocComment(..) => false, } } From 8aba7307df713697cbddd76f3709f54591f707ae Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:27:48 +0000 Subject: [PATCH 20/57] rustc_hir: fix typo in comment --- compiler/rustc_hir/src/hir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 5745f81db796..a1a4cea7eabc 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1958,7 +1958,7 @@ impl fmt::Display for ConstContext { } // NOTE: `IntoDiagArg` impl for `ConstContext` lives in `rustc_errors` -// due to a cyclical dependency between hir that crate. +// due to a cyclical dependency between hir and that crate. /// A literal. pub type Lit = Spanned; From 6c1df361380e08adb3e5f0872d323ad19beb7521 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:30:29 +0000 Subject: [PATCH 21/57] rustc_hir: flatten nested `if`s --- compiler/rustc_hir/src/hir.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index a1a4cea7eabc..742616e7f320 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3531,10 +3531,10 @@ impl<'hir> FnRetTy<'hir> { } pub fn is_suggestable_infer_ty(&self) -> Option<&'hir Ty<'hir>> { - if let Self::Return(ty) = self { - if ty.is_suggestable_infer_ty() { - return Some(*ty); - } + if let Self::Return(ty) = self + && ty.is_suggestable_infer_ty() + { + return Some(*ty); } None } @@ -4409,16 +4409,14 @@ impl<'hir> Node<'hir> { /// Get a `hir::Impl` if the node is an impl block for the given `trait_def_id`. pub fn impl_block_of_trait(self, trait_def_id: DefId) -> Option<&'hir Impl<'hir>> { - match self { - Node::Item(Item { kind: ItemKind::Impl(impl_block), .. }) - if impl_block - .of_trait - .and_then(|trait_ref| trait_ref.trait_def_id()) - .is_some_and(|trait_id| trait_id == trait_def_id) => - { - Some(impl_block) - } - _ => None, + if let Node::Item(Item { kind: ItemKind::Impl(impl_block), .. }) = self + && let Some(trait_ref) = impl_block.of_trait + && let Some(trait_id) = trait_ref.trait_def_id() + && trait_id == trait_def_id + { + Some(impl_block) + } else { + None } } From 056fe9650334b34420d1afbd366b86dd6c082a0e Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Tue, 28 Jan 2025 22:37:05 +0000 Subject: [PATCH 22/57] rustc_hir: remove some uneeded refs and derefs --- compiler/rustc_hir/src/hir.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 742616e7f320..ef1e28ae61fb 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3903,11 +3903,11 @@ pub struct FnHeader { impl FnHeader { pub fn is_async(&self) -> bool { - matches!(&self.asyncness, IsAsync::Async(_)) + matches!(self.asyncness, IsAsync::Async(_)) } pub fn is_const(&self) -> bool { - matches!(&self.constness, Constness::Const) + matches!(self.constness, Constness::Const) } pub fn is_unsafe(&self) -> bool { @@ -4003,16 +4003,16 @@ pub struct Impl<'hir> { impl ItemKind<'_> { pub fn generics(&self) -> Option<&Generics<'_>> { - Some(match *self { - ItemKind::Fn { ref generics, .. } - | ItemKind::TyAlias(_, ref generics) - | ItemKind::Const(_, ref generics, _) - | ItemKind::Enum(_, ref generics) - | ItemKind::Struct(_, ref generics) - | ItemKind::Union(_, ref generics) - | ItemKind::Trait(_, _, ref generics, _, _) - | ItemKind::TraitAlias(ref generics, _) - | ItemKind::Impl(Impl { ref generics, .. }) => generics, + Some(match self { + ItemKind::Fn { generics, .. } + | ItemKind::TyAlias(_, generics) + | ItemKind::Const(_, generics, _) + | ItemKind::Enum(_, generics) + | ItemKind::Struct(_, generics) + | ItemKind::Union(_, generics) + | ItemKind::Trait(_, _, generics, _, _) + | ItemKind::TraitAlias(generics, _) + | ItemKind::Impl(Impl { generics, .. }) => generics, _ => return None, }) } From 7eefa7671f59dba13a15fd6255684aa19ee05f7d Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 12 Dec 2024 10:37:37 +0000 Subject: [PATCH 23/57] Eliminate PatKind::Path --- clippy_lints/src/equatable_if_let.rs | 2 +- clippy_lints/src/manual_let_else.rs | 9 +++-- clippy_lints/src/manual_unwrap_or_default.rs | 4 +-- clippy_lints/src/matches/collapsible_match.rs | 8 +++-- clippy_lints/src/matches/manual_ok_err.rs | 13 +++++-- clippy_lints/src/matches/manual_unwrap_or.rs | 8 +++-- clippy_lints/src/matches/manual_utils.rs | 10 +++--- clippy_lints/src/matches/match_as_ref.rs | 4 +-- clippy_lints/src/matches/match_same_arms.rs | 8 +++-- clippy_lints/src/matches/match_wild_enum.rs | 11 ++++-- clippy_lints/src/matches/needless_match.rs | 12 +++++-- .../src/matches/redundant_pattern_match.rs | 36 ++++++++++++++----- clippy_lints/src/matches/single_match.rs | 21 +++++------ clippy_lints/src/option_if_let_else.rs | 10 ++++-- clippy_lints/src/use_self.rs | 4 +-- clippy_lints/src/utils/author.rs | 5 --- clippy_utils/src/hir_utils.rs | 2 -- clippy_utils/src/lib.rs | 25 ++++++++++--- 18 files changed, 135 insertions(+), 57 deletions(-) diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index 8a5cf7f56d5f..7ca2c9536998 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -56,7 +56,7 @@ fn unary_pattern(pat: &Pat<'_>) -> bool { PatKind::Struct(_, a, etc) => !etc && a.iter().all(|x| unary_pattern(x.pat)), PatKind::Tuple(a, etc) | PatKind::TupleStruct(_, a, etc) => etc.as_opt_usize().is_none() && array_rec(a), PatKind::Ref(x, _) | PatKind::Box(x) | PatKind::Deref(x) | PatKind::Guard(x, _) => unary_pattern(x), - PatKind::Path(_) | PatKind::Expr(_) => true, + PatKind::Expr(_) => true, } } diff --git a/clippy_lints/src/manual_let_else.rs b/clippy_lints/src/manual_let_else.rs index 8503dde3fb6b..274785061b3f 100644 --- a/clippy_lints/src/manual_let_else.rs +++ b/clippy_lints/src/manual_let_else.rs @@ -7,7 +7,7 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{is_lint_allowed, is_never_expr, msrvs, pat_and_expr_can_be_question_mark, peel_blocks}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind}; +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; @@ -292,7 +292,12 @@ fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>, check_types: boo // Only do the check if the type is "spelled out" in the pattern if !matches!( pat.kind, - PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..) + PatKind::Struct(..) + | PatKind::TupleStruct(..) + | PatKind::Expr(PatExpr { + kind: PatExprKind::Path(..), + .. + },) ) { return; } diff --git a/clippy_lints/src/manual_unwrap_or_default.rs b/clippy_lints/src/manual_unwrap_or_default.rs index 8f8390b6f3f9..7b95399c907c 100644 --- a/clippy_lints/src/manual_unwrap_or_default.rs +++ b/clippy_lints/src/manual_unwrap_or_default.rs @@ -1,6 +1,6 @@ use rustc_errors::Applicability; use rustc_hir::def::Res; -use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatKind, QPath}; +use rustc_hir::{Arm, Expr, ExprKind, HirId, LangItem, MatchSource, Pat, PatExpr, PatExprKind, PatKind, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::GenericArgKind; use rustc_session::declare_lint_pass; @@ -68,7 +68,7 @@ fn get_some<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option { } fn get_none<'tcx>(cx: &LateContext<'tcx>, arm: &Arm<'tcx>) -> Option<&'tcx Expr<'tcx>> { - if let PatKind::Path(QPath::Resolved(_, path)) = arm.pat.kind + if let PatKind::Expr(PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) = arm.pat.kind && let Some(def_id) = path.res.opt_def_id() // Since it comes from a pattern binding, we need to get the parent to actually match // against it. diff --git a/clippy_lints/src/matches/collapsible_match.rs b/clippy_lints/src/matches/collapsible_match.rs index 99a7b8c74be8..97e8423695d9 100644 --- a/clippy_lints/src/matches/collapsible_match.rs +++ b/clippy_lints/src/matches/collapsible_match.rs @@ -8,7 +8,7 @@ use clippy_utils::{ }; use rustc_errors::MultiSpan; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, Expr, HirId, Pat, PatKind}; +use rustc_hir::{Arm, Expr, HirId, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_span::Span; @@ -119,7 +119,11 @@ fn arm_is_wild_like(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { } match arm.pat.kind { PatKind::Binding(..) | PatKind::Wild => true, - PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone), + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(qpath), + hir_id, + .. + }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), _ => false, } } diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index b1a555b91d1b..3deaaf96c1e8 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -6,7 +6,7 @@ use rustc_ast::BindingMode; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind, Path, QPath}; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::Ty; use rustc_span::symbol::Ident; @@ -60,7 +60,16 @@ pub(crate) fn check_match(cx: &LateContext<'_>, expr: &Expr<'_>, scrutinee: &Exp /// accepted. fn is_variant_or_wildcard(cx: &LateContext<'_>, pat: &Pat<'_>, can_be_wild: bool, must_match_err: bool) -> bool { match pat.kind { - PatKind::Wild | PatKind::Path(..) | PatKind::Binding(_, _, _, None) if can_be_wild => true, + PatKind::Wild + | PatKind::Expr(PatExpr { + kind: PatExprKind::Path(_), + .. + }) + | PatKind::Binding(_, _, _, None) + if can_be_wild => + { + true + }, PatKind::TupleStruct(qpath, ..) => { is_res_lang_ctor(cx, cx.qpath_res(&qpath, pat.hir_id), ResultErr) == must_match_err }, diff --git a/clippy_lints/src/matches/manual_unwrap_or.rs b/clippy_lints/src/matches/manual_unwrap_or.rs index 59d375200117..b69294d567d1 100644 --- a/clippy_lints/src/matches/manual_unwrap_or.rs +++ b/clippy_lints/src/matches/manual_unwrap_or.rs @@ -7,7 +7,7 @@ use clippy_utils::{is_res_lang_ctor, path_to_local_id, peel_blocks, sugg}; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, ResultErr}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, Pat, PatKind}; +use rustc_hir::{Arm, Expr, Pat, PatExpr, PatExprKind, PatKind}; use rustc_lint::LateContext; use rustc_middle::ty::Ty; use rustc_span::sym; @@ -89,7 +89,11 @@ fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<(& if arms.len() == 2 && arms.iter().all(|arm| arm.guard.is_none()) && let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| match arm.pat.kind { - PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone), + PatKind::Expr(PatExpr { + hir_id, + kind: PatExprKind::Path(qpath), + .. + }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), PatKind::TupleStruct(ref qpath, [pat], _) => { matches!(pat.kind, PatKind::Wild) && is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr) diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 0b57740064c1..2ad55d9bf1fd 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -11,7 +11,7 @@ use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::def::Res; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -256,9 +256,11 @@ pub(super) fn try_parse_pattern<'tcx>( match pat.kind { PatKind::Wild => Some(OptionPat::Wild), PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt), - PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionNone) => { - Some(OptionPat::None) - }, + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(qpath), + hir_id, + .. + }) if is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone) => Some(OptionPat::None), PatKind::TupleStruct(ref qpath, [pattern], _) if is_res_lang_ctor(cx, cx.qpath_res(qpath, pat.hir_id), OptionSome) && pat.span.ctxt() == ctxt => { diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 6c123649afc2..b1889d26c932 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{is_res_lang_ctor, path_res, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatExpr, PatExprKind, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -59,7 +59,7 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { matches!( arm.pat.kind, - PatKind::Path(ref qpath) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionNone) + PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), .. }) if is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), LangItem::OptionNone) ) } diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 28e05c273d5c..41e4c75f843e 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -7,7 +7,7 @@ use rustc_arena::DroplessArena; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatExprKind, PatKind, RangeEnd}; +use rustc_hir::{Arm, Expr, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatExpr, PatExprKind, PatKind, RangeEnd}; use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty; @@ -292,7 +292,11 @@ impl<'a> NormalizedPat<'a> { Self::Tuple(var_id, pats) }, PatKind::Or(pats) => Self::Or(arena.alloc_from_iter(pats.iter().map(|pat| Self::from_pat(cx, arena, pat)))), - PatKind::Path(ref path) => Self::Path(cx.qpath_res(path, pat.hir_id).opt_def_id()), + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(path), + hir_id, + .. + }) => Self::Path(cx.qpath_res(path, *hir_id).opt_def_id()), PatKind::Tuple(pats, wild_idx) => { let field_count = match cx.typeck_results().pat_ty(pat).kind() { ty::Tuple(subs) => subs.len(), diff --git a/clippy_lints/src/matches/match_wild_enum.rs b/clippy_lints/src/matches/match_wild_enum.rs index 595655600890..11b588b33554 100644 --- a/clippy_lints/src/matches/match_wild_enum.rs +++ b/clippy_lints/src/matches/match_wild_enum.rs @@ -3,7 +3,7 @@ use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{is_refutable, peel_hir_pat_refs, recurse_or_patterns}; use rustc_errors::Applicability; use rustc_hir::def::{CtorKind, DefKind, Res}; -use rustc_hir::{Arm, Expr, PatKind, PathSegment, QPath, Ty, TyKind}; +use rustc_hir::{Arm, Expr, PatExpr, PatExprKind, PatKind, PathSegment, QPath, Ty, TyKind}; use rustc_lint::LateContext; use rustc_middle::ty::{self, VariantDef}; use rustc_span::sym; @@ -60,8 +60,13 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>]) { // covered by the set of guards that cover it, but that's really hard to do. recurse_or_patterns(arm.pat, |pat| { let path = match &peel_hir_pat_refs(pat).0.kind { - PatKind::Path(path) => { - let id = match cx.qpath_res(path, pat.hir_id) { + PatKind::Expr(PatExpr { + hir_id, + kind: PatExprKind::Path(path), + .. + }) => { + // FIXME(clippy): don't you want to use the hir id of the peeled pat? + let id = match cx.qpath_res(path, *hir_id) { Res::Def( DefKind::Const | DefKind::ConstParam | DefKind::AnonConst | DefKind::InlineConst, _, diff --git a/clippy_lints/src/matches/needless_match.rs b/clippy_lints/src/matches/needless_match.rs index 0d5575efc220..7e65d586110e 100644 --- a/clippy_lints/src/matches/needless_match.rs +++ b/clippy_lints/src/matches/needless_match.rs @@ -8,7 +8,9 @@ use clippy_utils::{ }; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionNone; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, ItemKind, Node, Pat, PatExprKind, PatKind, Path, QPath}; +use rustc_hir::{ + Arm, BindingMode, ByRef, Expr, ExprKind, ItemKind, Node, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, +}; use rustc_lint::LateContext; use rustc_span::sym; @@ -183,7 +185,13 @@ fn pat_same_as_expr(pat: &Pat<'_>, expr: &Expr<'_>) -> bool { return !matches!(annot, BindingMode(ByRef::Yes(_), _)) && pat_ident.name == first_seg.ident.name; }, // Example: `Custom::TypeA => Custom::TypeB`, or `None => None` - (PatKind::Path(QPath::Resolved(_, p_path)), ExprKind::Path(QPath::Resolved(_, e_path))) => { + ( + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(QPath::Resolved(_, p_path)), + .. + }), + ExprKind::Path(QPath::Resolved(_, e_path)), + ) => { return over(p_path.segments, e_path.segments, |p_seg, e_seg| { p_seg.ident.name == e_seg.ident.name }); diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index edac97344a03..393399660131 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -9,7 +9,7 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatExprKind, PatKind, QPath, UnOp}; +use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatExpr, PatExprKind, PatKind, QPath, UnOp}; use rustc_lint::LateContext; use rustc_middle::ty::{self, GenericArgKind, Ty}; use rustc_span::{Span, Symbol, sym}; @@ -149,8 +149,12 @@ fn find_method_and_type<'tcx>( None } }, - PatKind::Path(ref path) => { - if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(path, check_pat.hir_id) + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(path), + hir_id, + .. + }) => { + if let Res::Def(DefKind::Ctor(..), ctor_id) = cx.qpath_res(path, *hir_id) && let Some(variant_id) = cx.tcx.opt_parent(ctor_id) { let method = if cx.tcx.lang_items().option_none_variant() == Some(variant_id) { @@ -351,10 +355,20 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right)) - | (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _)) - if patterns.len() == 1 => - { + ( + PatKind::TupleStruct(path_left, patterns, _), + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(path_right), + .. + }), + ) + | ( + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(path_left), + .. + }), + PatKind::TupleStruct(path_right, patterns, _), + ) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { find_good_method_for_match( cx, @@ -389,7 +403,13 @@ fn found_good_method<'tcx>( None } }, - (PatKind::Path(path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), + ( + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(path_left), + .. + }), + PatKind::Wild, + ) => get_good_method(cx, arms, path_left), _ => None, } } diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 38f876fed802..2f46eaaabb36 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -114,7 +114,7 @@ fn report_single_pattern(cx: &LateContext<'_>, ex: &Expr<'_>, arm: &Arm<'_>, exp } let (pat, pat_ref_count) = peel_hir_pat_refs(arm.pat); - let (msg, sugg) = if let PatKind::Path(_) | PatKind::Expr(_) = pat.kind + let (msg, sugg) = if let PatKind::Expr(_) = pat.kind && let (ty, ty_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(ex)) && let Some(spe_trait_id) = cx.tcx.lang_items().structural_peq_trait() && let Some(pe_trait_id) = cx.tcx.lang_items().eq_trait() @@ -331,14 +331,16 @@ impl<'a> PatState<'a> { #[expect(clippy::similar_names)] fn add_pat<'tcx>(&mut self, cx: &'a PatCtxt<'tcx>, pat: &'tcx Pat<'_>) -> bool { match pat.kind { - PatKind::Path(_) - if match *cx.typeck.pat_ty(pat).peel_refs().kind() { - ty::Adt(adt, _) => adt.is_enum() || (adt.is_struct() && !adt.non_enum_variant().fields.is_empty()), - ty::Tuple(tys) => !tys.is_empty(), - ty::Array(_, len) => len.try_to_target_usize(cx.tcx) != Some(1), - ty::Slice(..) => true, - _ => false, - } => + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(_), + .. + }) if match *cx.typeck.pat_ty(pat).peel_refs().kind() { + ty::Adt(adt, _) => adt.is_enum() || (adt.is_struct() && !adt.non_enum_variant().fields.is_empty()), + ty::Tuple(tys) => !tys.is_empty(), + ty::Array(_, len) => len.try_to_target_usize(cx.tcx) != Some(1), + ty::Slice(..) => true, + _ => false, + } => { matches!(self, Self::Wild) }, @@ -386,7 +388,6 @@ impl<'a> PatState<'a> { | PatKind::Binding(_, _, _, None) | PatKind::Expr(_) | PatKind::Range(..) - | PatKind::Path(_) | PatKind::Never | PatKind::Err(_) => { *self = PatState::Wild; diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 6d9e75f51d66..de9f055863cb 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -7,7 +7,9 @@ use clippy_utils::{ use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::def::Res; -use rustc_hir::{Arm, BindingMode, Expr, ExprKind, MatchSource, Mutability, Pat, PatKind, Path, QPath, UnOp}; +use rustc_hir::{ + Arm, BindingMode, Expr, ExprKind, MatchSource, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, UnOp, +}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::SyntaxContext; @@ -281,7 +283,11 @@ fn try_convert_match<'tcx>( fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { match arm.pat.kind { - PatKind::Path(ref qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), OptionNone), + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(qpath), + hir_id, + .. + }) => is_res_lang_ctor(cx, cx.qpath_res(qpath, *hir_id), OptionNone), PatKind::TupleStruct(ref qpath, [first_pat], _) => { is_res_lang_ctor(cx, cx.qpath_res(qpath, arm.pat.hir_id), ResultErr) && matches!(first_pat.kind, PatKind::Wild) diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 84b6430294f3..47ce2243aa09 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -10,7 +10,7 @@ use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_ty}; use rustc_hir::{ self as hir, AmbigArg, Expr, ExprKind, FnRetTy, FnSig, GenericArgsParentheses, GenericParam, GenericParamKind, - HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind, + HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath, Ty, TyKind, }; use rustc_hir_analysis::lower_ty; use rustc_lint::{LateContext, LateLintPass}; @@ -258,7 +258,7 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf { && self.msrv.meets(msrvs::TYPE_ALIAS_ENUM_VARIANTS) && let Some(&StackItem::Check { impl_id, .. }) = self.stack.last() // get the path from the pattern - && let PatKind::Path(QPath::Resolved(_, path)) + && let PatKind::Expr(&PatExpr { kind: PatExprKind::Path(QPath::Resolved(_, path)), .. }) | PatKind::TupleStruct(QPath::Resolved(_, path), _, _) | PatKind::Struct(QPath::Resolved(_, path), _, _) = pat.kind && cx.typeck_results().pat_ty(pat) == cx.tcx.type_of(impl_id).instantiate_identity() diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 4dcc8ac7fb0a..6bad78cf8718 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -708,11 +708,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.qpath(qpath); self.slice(fields, |pat| self.pat(pat)); }, - PatKind::Path(ref qpath) => { - bind!(self, qpath); - kind!("Path(ref {qpath})"); - self.qpath(qpath); - }, PatKind::Tuple(fields, skip_pos) => { bind!(self, fields); kind!("Tuple({fields}, {skip_pos:?})"); diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index d76231a6eeaa..d0eb5318e645 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -524,7 +524,6 @@ impl HirEqInterExpr<'_, '_, '_> { } eq }, - (PatKind::Path(l), PatKind::Path(r)) => self.eq_qpath(l, r), (&PatKind::Expr(l), &PatKind::Expr(r)) => self.eq_pat_expr(l, r), (&PatKind::Tuple(l, ls), &PatKind::Tuple(r, rs)) => ls == rs && over(l, r, |l, r| self.eq_pat(l, r)), (&PatKind::Range(ref ls, ref le, li), &PatKind::Range(ref rs, ref re, ri)) => { @@ -1120,7 +1119,6 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_pat(pat); } }, - PatKind::Path(ref qpath) => self.hash_qpath(qpath), PatKind::Range(s, e, i) => { if let Some(s) = s { self.hash_pat_expr(s); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e471dfb6ef19..5a5227af9074 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -106,8 +106,8 @@ use rustc_hir::{ self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext, Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind, 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, def, + PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, + TraitItemRef, TraitRef, TyKind, UnOp, def, }; use rustc_lexer::{TokenKind, tokenize}; use rustc_lint::{LateContext, Level, Lint, LintContext}; @@ -560,7 +560,20 @@ macro_rules! maybe_path { }; } maybe_path!(Expr, ExprKind); -maybe_path!(Pat, PatKind); +impl<'hir> MaybePath<'hir> for Pat<'hir> { + fn hir_id(&self) -> HirId { + self.hir_id + } + fn qpath_opt(&self) -> Option<&QPath<'hir>> { + match &self.kind { + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(qpath), + .. + }) => Some(qpath), + _ => None, + } + } +} maybe_path!(Ty, TyKind); /// If `maybe_path` is a path node, resolves it, otherwise returns `Res::Err` @@ -1753,7 +1766,11 @@ pub fn is_refutable(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { PatKind::Wild | PatKind::Never => false, // If `!` typechecked then the type is empty, so not refutable. PatKind::Binding(_, _, _, pat) => pat.is_some_and(|pat| is_refutable(cx, pat)), PatKind::Box(pat) | PatKind::Ref(pat, _) => is_refutable(cx, pat), - PatKind::Path(ref qpath) => is_enum_variant(cx, qpath, pat.hir_id), + PatKind::Expr(PatExpr { + kind: PatExprKind::Path(qpath), + hir_id, + .. + }) => is_enum_variant(cx, qpath, *hir_id), PatKind::Or(pats) => { // TODO: should be the honest check, that pats is exhaustive set are_refutable(cx, pats) From 84fb6b165176d4349377862a1587c522a3ac5ae8 Mon Sep 17 00:00:00 2001 From: Aaron Ang <67321817+aaron-ang@users.noreply.github.com> Date: Sun, 26 Jan 2025 15:19:10 -0800 Subject: [PATCH 24/57] Add new lint `return_and_then` --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/methods/mod.rs | 55 +++++++++- clippy_lints/src/methods/return_and_then.rs | 67 ++++++++++++ .../src/methods/unnecessary_lazy_eval.rs | 6 +- tests/ui/return_and_then.fixed | 67 ++++++++++++ tests/ui/return_and_then.rs | 63 +++++++++++ tests/ui/return_and_then.stderr | 101 ++++++++++++++++++ 8 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 clippy_lints/src/methods/return_and_then.rs create mode 100644 tests/ui/return_and_then.fixed create mode 100644 tests/ui/return_and_then.rs create mode 100644 tests/ui/return_and_then.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index bc42c07224e1..cb149ccfeead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6026,6 +6026,7 @@ Released 2018-09-13 [`result_map_unwrap_or_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_map_unwrap_or_else [`result_unit_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err [`result_unwrap_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_unwrap_used +[`return_and_then`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_and_then [`return_self_not_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#return_self_not_must_use [`reverse_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#reverse_range_loop [`reversed_empty_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#reversed_empty_ranges diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 86bcf8edd578..1ce6f1256177 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -463,6 +463,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::REPEAT_ONCE_INFO, crate::methods::RESULT_FILTER_MAP_INFO, crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO, + crate::methods::RETURN_AND_THEN_INFO, crate::methods::SEARCH_IS_SOME_INFO, crate::methods::SEEK_FROM_CURRENT_INFO, crate::methods::SEEK_TO_START_INSTEAD_OF_REWIND_INFO, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 00be6b81e100..6dbfbc2ca3b4 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -95,6 +95,7 @@ mod readonly_write_lock; mod redundant_as_str; mod repeat_once; mod result_map_or_else_none; +mod return_and_then; mod search_is_some; mod seek_from_current; mod seek_to_start_instead_of_rewind; @@ -4392,6 +4393,46 @@ declare_clippy_lint! { "slicing a string and immediately calling as_bytes is less efficient and can lead to panics" } +declare_clippy_lint! { + /// ### What it does + /// + /// Detect functions that end with `Option::and_then` or `Result::and_then`, and suggest using a question mark (`?`) instead. + /// + /// ### Why is this bad? + /// + /// The `and_then` method is used to chain a computation that returns an `Option` or a `Result`. + /// This can be replaced with the `?` operator, which is more concise and idiomatic. + /// + /// ### Example + /// + /// ```no_run + /// fn test(opt: Option) -> Option { + /// opt.and_then(|n| { + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// }) + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn test(opt: Option) -> Option { + /// let n = opt?; + /// if n > 1 { + /// Some(n + 1) + /// } else { + /// None + /// } + /// } + /// ``` + #[clippy::version = "1.86.0"] + pub RETURN_AND_THEN, + restriction, + "using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4561,6 +4602,7 @@ impl_lint_pass!(Methods => [ USELESS_NONZERO_NEW_UNCHECKED, MANUAL_REPEAT_N, SLICED_STRING_AS_BYTES, + RETURN_AND_THEN, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4790,7 +4832,10 @@ impl Methods { let biom_option_linted = bind_instead_of_map::check_and_then_some(cx, expr, recv, arg); let biom_result_linted = bind_instead_of_map::check_and_then_ok(cx, expr, recv, arg); if !biom_option_linted && !biom_result_linted { - unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + let ule_and_linted = unnecessary_lazy_eval::check(cx, expr, recv, arg, "and"); + if !ule_and_linted { + return_and_then::check(cx, expr, recv, arg); + } } }, ("any", [arg]) => { @@ -5004,7 +5049,9 @@ impl Methods { get_first::check(cx, expr, recv, arg); get_last_with_len::check(cx, expr, recv, arg); }, - ("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"), + ("get_or_insert_with", [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"); + }, ("hash", [arg]) => { unit_hash::check(cx, expr, recv, arg); }, @@ -5145,7 +5192,9 @@ impl Methods { }, _ => iter_nth_zero::check(cx, expr, recv, n_arg), }, - ("ok_or_else", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"), + ("ok_or_else", [arg]) => { + unnecessary_lazy_eval::check(cx, expr, recv, arg, "ok_or"); + }, ("open", [_]) => { open_options::check(cx, expr, recv); }, diff --git a/clippy_lints/src/methods/return_and_then.rs b/clippy_lints/src/methods/return_and_then.rs new file mode 100644 index 000000000000..7b1199ad1e2d --- /dev/null +++ b/clippy_lints/src/methods/return_and_then.rs @@ -0,0 +1,67 @@ +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArg, Ty}; +use rustc_span::sym; +use std::ops::ControlFlow; + +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability}; +use clippy_utils::ty::get_type_diagnostic_name; +use clippy_utils::visitors::for_each_unconsumed_temporary; +use clippy_utils::{is_expr_final_block_expr, peel_blocks}; + +use super::RETURN_AND_THEN; + +/// lint if `and_then` is the last expression in a block, and +/// there are no references or temporaries in the receiver +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &hir::Expr<'_>, + recv: &'tcx hir::Expr<'tcx>, + arg: &'tcx hir::Expr<'_>, +) { + if !is_expr_final_block_expr(cx.tcx, expr) { + return; + } + + let recv_type = cx.typeck_results().expr_ty(recv); + if !matches!(get_type_diagnostic_name(cx, recv_type), Some(sym::Option | sym::Result)) { + return; + } + + let has_ref_type = matches!(recv_type.kind(), ty::Adt(_, args) if args + .first() + .and_then(|arg0: &GenericArg<'tcx>| GenericArg::as_type(*arg0)) + .is_some_and(Ty::is_ref)); + let has_temporaries = for_each_unconsumed_temporary(cx, recv, |_| ControlFlow::Break(())).is_break(); + if has_ref_type && has_temporaries { + return; + } + + let hir::ExprKind::Closure(&hir::Closure { body, fn_decl, .. }) = arg.kind else { + return; + }; + + let closure_arg = fn_decl.inputs[0]; + let closure_expr = peel_blocks(cx.tcx.hir().body(body).value); + + let mut applicability = Applicability::MachineApplicable; + let arg_snip = snippet_with_applicability(cx, closure_arg.span, "_", &mut applicability); + let recv_snip = snippet_with_applicability(cx, recv.span, "_", &mut applicability); + let body_snip = snippet_with_applicability(cx, closure_expr.span, "..", &mut applicability); + let inner = match body_snip.strip_prefix('{').and_then(|s| s.strip_suffix('}')) { + Some(s) => s.trim_start_matches('\n').trim_end(), + None => &body_snip, + }; + + let msg = "use the question mark operator instead of an `and_then` call"; + let sugg = format!( + "let {} = {}?;\n{}", + arg_snip, + recv_snip, + reindent_multiline(inner.into(), false, indent_of(cx, expr.span)) + ); + + span_lint_and_sugg(cx, RETURN_AND_THEN, expr.span, msg, "try", sugg, applicability); +} diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 1673a6f8b3a4..7af550fa7c68 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -18,7 +18,7 @@ pub(super) fn check<'tcx>( recv: &'tcx hir::Expr<'_>, arg: &'tcx hir::Expr<'_>, simplify_using: &str, -) { +) -> bool { let is_option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Option); let is_result = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result); let is_bool = cx.typeck_results().expr_ty(recv).is_bool(); @@ -29,7 +29,7 @@ pub(super) fn check<'tcx>( let body_expr = &body.value; if usage::BindingUsageFinder::are_params_used(cx, body) || is_from_proc_macro(cx, expr) { - return; + return false; } if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { @@ -71,8 +71,10 @@ pub(super) fn check<'tcx>( applicability, ); }); + return true; } } } } + false } diff --git a/tests/ui/return_and_then.fixed b/tests/ui/return_and_then.fixed new file mode 100644 index 000000000000..9736a51ac868 --- /dev/null +++ b/tests/ui/return_and_then.fixed @@ -0,0 +1,67 @@ +#![warn(clippy::return_and_then)] + +fn main() { + fn test_opt_block(opt: Option) -> Option { + let n = opt?; + let mut ret = n + 1; + ret += n; + if n > 1 { Some(ret) } else { None } + } + + fn test_opt_func(opt: Option) -> Option { + let n = opt?; + test_opt_block(Some(n)) + } + + fn test_call_chain() -> Option { + let n = gen_option(1)?; + test_opt_block(Some(n)) + } + + fn test_res_block(opt: Result) -> Result { + let n = opt?; + if n > 1 { Ok(n + 1) } else { Err(n) } + } + + fn test_res_func(opt: Result) -> Result { + let n = opt?; + test_res_block(Ok(n)) + } + + fn test_ref_only() -> Option { + // ref: empty string + let x = Some("")?; + if x.len() > 2 { Some(3) } else { None } + } + + fn test_tmp_only() -> Option { + // unused temporary: vec![1, 2, 4] + let x = Some(match (vec![1, 2, 3], vec![1, 2, 4]) { + (a, _) if a.len() > 1 => a, + (_, b) => b, + })?; + if x.len() > 2 { Some(3) } else { None } + } + + // should not lint + fn test_tmp_ref() -> Option { + String::from("") + .strip_prefix("<") + .and_then(|s| s.strip_suffix(">").map(String::from)) + } + + // should not lint + fn test_unconsumed_tmp() -> Option { + [1, 2, 3] + .iter() + .map(|x| x + 1) + .collect::>() // temporary Vec created here + .as_slice() // creates temporary slice + .first() // creates temporary reference + .and_then(|x| test_opt_block(Some(*x))) + } +} + +fn gen_option(n: i32) -> Option { + Some(n) +} diff --git a/tests/ui/return_and_then.rs b/tests/ui/return_and_then.rs new file mode 100644 index 000000000000..8bcbdfc3a632 --- /dev/null +++ b/tests/ui/return_and_then.rs @@ -0,0 +1,63 @@ +#![warn(clippy::return_and_then)] + +fn main() { + fn test_opt_block(opt: Option) -> Option { + opt.and_then(|n| { + let mut ret = n + 1; + ret += n; + if n > 1 { Some(ret) } else { None } + }) + } + + fn test_opt_func(opt: Option) -> Option { + opt.and_then(|n| test_opt_block(Some(n))) + } + + fn test_call_chain() -> Option { + gen_option(1).and_then(|n| test_opt_block(Some(n))) + } + + fn test_res_block(opt: Result) -> Result { + opt.and_then(|n| if n > 1 { Ok(n + 1) } else { Err(n) }) + } + + fn test_res_func(opt: Result) -> Result { + opt.and_then(|n| test_res_block(Ok(n))) + } + + fn test_ref_only() -> Option { + // ref: empty string + Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) + } + + fn test_tmp_only() -> Option { + // unused temporary: vec![1, 2, 4] + Some(match (vec![1, 2, 3], vec![1, 2, 4]) { + (a, _) if a.len() > 1 => a, + (_, b) => b, + }) + .and_then(|x| if x.len() > 2 { Some(3) } else { None }) + } + + // should not lint + fn test_tmp_ref() -> Option { + String::from("") + .strip_prefix("<") + .and_then(|s| s.strip_suffix(">").map(String::from)) + } + + // should not lint + fn test_unconsumed_tmp() -> Option { + [1, 2, 3] + .iter() + .map(|x| x + 1) + .collect::>() // temporary Vec created here + .as_slice() // creates temporary slice + .first() // creates temporary reference + .and_then(|x| test_opt_block(Some(*x))) + } +} + +fn gen_option(n: i32) -> Option { + Some(n) +} diff --git a/tests/ui/return_and_then.stderr b/tests/ui/return_and_then.stderr new file mode 100644 index 000000000000..b2e8bf2ca45a --- /dev/null +++ b/tests/ui/return_and_then.stderr @@ -0,0 +1,101 @@ +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:5:9 + | +LL | / opt.and_then(|n| { +LL | | let mut ret = n + 1; +LL | | ret += n; +LL | | if n > 1 { Some(ret) } else { None } +LL | | }) + | |__________^ + | + = note: `-D clippy::return-and-then` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::return_and_then)]` +help: try + | +LL ~ let n = opt?; +LL + let mut ret = n + 1; +LL + ret += n; +LL + if n > 1 { Some(ret) } else { None } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:13:9 + | +LL | opt.and_then(|n| test_opt_block(Some(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + test_opt_block(Some(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:17:9 + | +LL | gen_option(1).and_then(|n| test_opt_block(Some(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = gen_option(1)?; +LL + test_opt_block(Some(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:21:9 + | +LL | opt.and_then(|n| if n > 1 { Ok(n + 1) } else { Err(n) }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + if n > 1 { Ok(n + 1) } else { Err(n) } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:25:9 + | +LL | opt.and_then(|n| test_res_block(Ok(n))) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let n = opt?; +LL + test_res_block(Ok(n)) + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:30:9 + | +LL | Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None }) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL ~ let x = Some("")?; +LL + if x.len() > 2 { Some(3) } else { None } + | + +error: use the question mark operator instead of an `and_then` call + --> tests/ui/return_and_then.rs:35:9 + | +LL | / Some(match (vec![1, 2, 3], vec![1, 2, 4]) { +LL | | (a, _) if a.len() > 1 => a, +LL | | (_, b) => b, +LL | | }) +LL | | .and_then(|x| if x.len() > 2 { Some(3) } else { None }) + | |_______________________________________________________________^ + | +help: try + | +LL ~ let x = Some(match (vec![1, 2, 3], vec![1, 2, 4]) { +LL + (a, _) if a.len() > 1 => a, +LL + (_, b) => b, +LL + })?; +LL + if x.len() > 2 { Some(3) } else { None } + | + +error: aborting due to 7 previous errors + From 78d6b2ea4ea69308d47e96ec1af3c54d51172c70 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 29 Jan 2025 19:02:18 +0100 Subject: [PATCH 25/57] Do not remove semicolon if it changes the block type Removing the semicolon of the last statement of an expressionless block may change the block type even if the statement's type is `()`. If the block type is `!` because of a systematic early return, typing it as `()` may make it incompatible with the expected type for the block (to which `!` is cast). --- clippy_lints/src/unnecessary_semicolon.rs | 38 ++++++++++++------- .../unnecessary_semicolon.edition2021.fixed | 6 +++ .../unnecessary_semicolon.edition2024.fixed | 6 +++ tests/ui/unnecessary_semicolon.rs | 6 +++ 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/unnecessary_semicolon.rs b/clippy_lints/src/unnecessary_semicolon.rs index efbc536dcb4d..e5267620c4fb 100644 --- a/clippy_lints/src/unnecessary_semicolon.rs +++ b/clippy_lints/src/unnecessary_semicolon.rs @@ -37,7 +37,7 @@ declare_clippy_lint! { #[derive(Default)] pub struct UnnecessarySemicolon { - last_statements: Vec, + last_statements: Vec<(HirId, bool)>, } impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]); @@ -45,27 +45,25 @@ impl_lint_pass!(UnnecessarySemicolon => [UNNECESSARY_SEMICOLON]); impl UnnecessarySemicolon { /// Enter or leave a block, remembering the last statement of the block. fn handle_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>, enter: bool) { - // Up to edition 2021, removing the semicolon of the last statement of a block - // may result in the scrutinee temporary values to live longer than the block - // variables. To avoid this problem, we do not lint the last statement of an - // expressionless block. - if cx.tcx.sess.edition() <= Edition2021 - && block.expr.is_none() + // The last statement of an expressionless block deserves a special treatment. + if block.expr.is_none() && let Some(last_stmt) = block.stmts.last() { if enter { - self.last_statements.push(last_stmt.hir_id); + let block_ty = cx.typeck_results().node_type(block.hir_id); + self.last_statements.push((last_stmt.hir_id, block_ty.is_unit())); } else { self.last_statements.pop(); } } } - /// Checks if `stmt` is the last statement in an expressionless block for edition ≤ 2021. - fn is_last_in_block(&self, stmt: &Stmt<'_>) -> bool { + /// Checks if `stmt` is the last statement in an expressionless block. In this case, + /// return `Some` with a boolean which is `true` if the block type is `()`. + fn is_last_in_block(&self, stmt: &Stmt<'_>) -> Option { self.last_statements .last() - .is_some_and(|last_stmt_id| last_stmt_id == &stmt.hir_id) + .and_then(|&(stmt_id, is_unit)| (stmt_id == stmt.hir_id).then_some(is_unit)) } } @@ -90,8 +88,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessarySemicolon { ) && cx.typeck_results().expr_ty(expr) == cx.tcx.types.unit { - if self.is_last_in_block(stmt) && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { - return; + if let Some(block_is_unit) = self.is_last_in_block(stmt) { + if cx.tcx.sess.edition() <= Edition2021 && leaks_droppable_temporary_with_limited_lifetime(cx, expr) { + // The expression contains temporaries with limited lifetimes in edition lower than 2024. Those may + // survive until after the end of the current scope instead of until the end of the statement, so do + // not lint this situation. + return; + } + + if !block_is_unit { + // Although the expression returns `()`, the block doesn't. This may happen if the expression + // returns early in all code paths, such as a `return value` in the condition of an `if` statement, + // in which case the block type would be `!`. Do not lint in this case, as the statement would + // become the block expression; the block type would become `()` and this may not type correctly + // if the expected type for the block is not `()`. + return; + } } let semi_span = expr.span.shrink_to_hi().to(stmt.span.shrink_to_hi()); diff --git a/tests/ui/unnecessary_semicolon.edition2021.fixed b/tests/ui/unnecessary_semicolon.edition2021.fixed index 7a3b79553dea..343c88b98155 100644 --- a/tests/ui/unnecessary_semicolon.edition2021.fixed +++ b/tests/ui/unnecessary_semicolon.edition2021.fixed @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, } } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} diff --git a/tests/ui/unnecessary_semicolon.edition2024.fixed b/tests/ui/unnecessary_semicolon.edition2024.fixed index d186d5e7ebc4..1cba5760eb0a 100644 --- a/tests/ui/unnecessary_semicolon.edition2024.fixed +++ b/tests/ui/unnecessary_semicolon.edition2024.fixed @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, } } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} diff --git a/tests/ui/unnecessary_semicolon.rs b/tests/ui/unnecessary_semicolon.rs index 3028c5b27b34..6abbbd79aaf2 100644 --- a/tests/ui/unnecessary_semicolon.rs +++ b/tests/ui/unnecessary_semicolon.rs @@ -55,3 +55,9 @@ fn no_borrow_issue(a: u32, b: u32) { None => {}, }; } + +fn issue14100() -> bool { + // Removing the `;` would make the block type be `()` instead of `!`, and this could no longer be + // cast into the `bool` function return type. + if return true {}; +} From e4505fb670e48d2752d13b182caed294e0d01d14 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Wed, 29 Jan 2025 23:33:35 +0100 Subject: [PATCH 26/57] =?UTF-8?q?Include=20generic=20arguments=20when=20su?= =?UTF-8?q?ggesting=20a=20closure=20=CE=B7-reduction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clippy_lints/src/eta_reduction.rs | 13 +++++--- tests/ui/eta.fixed | 5 +++ tests/ui/eta.rs | 5 +++ tests/ui/eta.stderr | 54 +++++++++++++++++-------------- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 52b699274bbb..f90bf9157aad 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::higher::VecArgs; -use clippy_utils::source::snippet_opt; +use clippy_utils::source::{snippet_opt, snippet_with_applicability}; use clippy_utils::ty::get_type_diagnostic_name; use clippy_utils::usage::{local_used_after_expr, local_used_in}; use clippy_utils::{ get_path_from_caller_to_method_type, is_adjusted, is_no_std_crate, path_to_local, path_to_local_id, }; use rustc_errors::Applicability; -use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Safety, TyKind}; +use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, GenericArgs, Param, PatKind, QPath, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ @@ -239,6 +239,11 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx && !cx.tcx.has_attr(method_def_id, sym::track_caller) && check_sig(closure_sig, cx.tcx.fn_sig(method_def_id).skip_binder().skip_binder()) { + let mut app = Applicability::MachineApplicable; + let generic_args = match path.args.and_then(GenericArgs::span_ext) { + Some(span) => format!("::{}", snippet_with_applicability(cx, span, "<..>", &mut app)), + None => String::new(), + }; span_lint_and_then( cx, REDUNDANT_CLOSURE_FOR_METHOD_CALLS, @@ -251,8 +256,8 @@ fn check_closure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tcx diag.span_suggestion( expr.span, "replace the closure with the method itself", - format!("{}::{}", type_name, path.ident.name), - Applicability::MachineApplicable, + format!("{}::{}{}", type_name, path.ident.name, generic_args), + app, ); }, ); diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index f1baf28200e9..abccc30ef87d 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -116,6 +116,11 @@ fn test_redundant_closures_containing_method_calls() { t.iter().filter(|x| x.trait_foo_ref()); t.iter().map(|x| x.trait_foo_ref()); } + + fn issue14096() { + let x = Some("42"); + let _ = x.map(str::parse::); + } } struct Thunk(Box T>); diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index c52a51880bfc..9bcee4eba340 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -116,6 +116,11 @@ fn test_redundant_closures_containing_method_calls() { t.iter().filter(|x| x.trait_foo_ref()); t.iter().map(|x| x.trait_foo_ref()); } + + fn issue14096() { + let x = Some("42"); + let _ = x.map(|x| x.parse::()); + } } struct Thunk(Box T>); diff --git a/tests/ui/eta.stderr b/tests/ui/eta.stderr index 1731a4377f53..ac58e87bc5ec 100644 --- a/tests/ui/eta.stderr +++ b/tests/ui/eta.stderr @@ -71,142 +71,148 @@ LL | let e: std::vec::Vec = vec!['a', 'b', 'c'].iter().map(|c| c.to_as | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `char::to_ascii_uppercase` error: redundant closure - --> tests/ui/eta.rs:169:22 + --> tests/ui/eta.rs:122:23 + | +LL | let _ = x.map(|x| x.parse::()); + | ^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `str::parse::` + +error: redundant closure + --> tests/ui/eta.rs:174:22 | LL | requires_fn_once(|| x()); | ^^^^^^ help: replace the closure with the function itself: `x` error: redundant closure - --> tests/ui/eta.rs:176:27 + --> tests/ui/eta.rs:181:27 | LL | let a = Some(1u8).map(|a| foo_ptr(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `foo_ptr` error: redundant closure - --> tests/ui/eta.rs:181:27 + --> tests/ui/eta.rs:186:27 | LL | let a = Some(1u8).map(|a| closure(a)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `closure` error: redundant closure - --> tests/ui/eta.rs:213:28 + --> tests/ui/eta.rs:218:28 | LL | x.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:214:28 + --> tests/ui/eta.rs:219:28 | LL | y.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut add_to_res` error: redundant closure - --> tests/ui/eta.rs:215:28 + --> tests/ui/eta.rs:220:28 | LL | z.into_iter().for_each(|x| add_to_res(x)); | ^^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `add_to_res` error: redundant closure - --> tests/ui/eta.rs:222:21 + --> tests/ui/eta.rs:227:21 | LL | Some(1).map(|n| closure(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut closure` error: redundant closure - --> tests/ui/eta.rs:226:21 + --> tests/ui/eta.rs:231:21 | LL | Some(1).map(|n| in_loop(n)); | ^^^^^^^^^^^^^^ help: replace the closure with the function itself: `in_loop` error: redundant closure - --> tests/ui/eta.rs:319:18 + --> tests/ui/eta.rs:324:18 | LL | takes_fn_mut(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:322:19 + --> tests/ui/eta.rs:327:19 | LL | takes_fn_once(|| f()); | ^^^^^^ help: replace the closure with the function itself: `&mut f` error: redundant closure - --> tests/ui/eta.rs:326:26 + --> tests/ui/eta.rs:331:26 | LL | move || takes_fn_mut(|| f_used_once()) | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `&mut f_used_once` error: redundant closure - --> tests/ui/eta.rs:338:19 + --> tests/ui/eta.rs:343:19 | LL | array_opt.map(|a| a.as_slice()); | ^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8; 3]>::as_slice` error: redundant closure - --> tests/ui/eta.rs:341:19 + --> tests/ui/eta.rs:346:19 | LL | slice_opt.map(|s| s.len()); | ^^^^^^^^^^^ help: replace the closure with the method itself: `<[u8]>::len` error: redundant closure - --> tests/ui/eta.rs:344:17 + --> tests/ui/eta.rs:349:17 | LL | ptr_opt.map(|p| p.is_null()); | ^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `<*const usize>::is_null` error: redundant closure - --> tests/ui/eta.rs:348:17 + --> tests/ui/eta.rs:353:17 | LL | dyn_opt.map(|d| d.method_on_dyn()); | ^^^^^^^^^^^^^^^^^^^^^ help: replace the closure with the method itself: `::method_on_dyn` error: redundant closure - --> tests/ui/eta.rs:408:19 + --> tests/ui/eta.rs:413:19 | LL | let _ = f(&0, |x, y| f2(x, y)); | ^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `f2` error: redundant closure - --> tests/ui/eta.rs:436:22 + --> tests/ui/eta.rs:441:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `Test::method` error: redundant closure - --> tests/ui/eta.rs:440:22 + --> tests/ui/eta.rs:445:22 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `super::Outer::method` error: redundant closure - --> tests/ui/eta.rs:453:18 + --> tests/ui/eta.rs:458:18 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `test_mod::Test::method` error: redundant closure - --> tests/ui/eta.rs:460:30 + --> tests/ui/eta.rs:465:30 | LL | test.map(|t| t.method()) | ^^^^^^^^^^^^^^ help: replace the closure with the method itself: `crate::issue_10854::d::Test::method` error: redundant closure - --> tests/ui/eta.rs:479:38 + --> tests/ui/eta.rs:484:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `&f` error: redundant closure - --> tests/ui/eta.rs:483:38 + --> tests/ui/eta.rs:488:38 | LL | let x = Box::new(|| None.map(|x| f(x))); | ^^^^^^^^ help: replace the closure with the function itself: `f` error: redundant closure - --> tests/ui/eta.rs:500:35 + --> tests/ui/eta.rs:505:35 | LL | let _field = bind.or_else(|| get_default()).unwrap(); | ^^^^^^^^^^^^^^^^ help: replace the closure with the function itself: `get_default` -error: aborting due to 34 previous errors +error: aborting due to 35 previous errors From f8be51862aa51998a0954f53540bdf7d4f002bbd Mon Sep 17 00:00:00 2001 From: Taiki Endo Date: Thu, 30 Jan 2025 23:32:05 +0900 Subject: [PATCH 27/57] Move mutex_integer to restriction and improve mutex_{integer,atomic} docs --- clippy_lints/src/mutex_atomic.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 86c084423b71..49fd29d1dd6d 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -18,11 +18,14 @@ declare_clippy_lint! { /// /// On the other hand, `Mutex`es are, in general, easier to /// verify correctness. An atomic does not behave the same as - /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s commentary for more details. + /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s + /// commentary for more details. /// /// ### Known problems - /// This lint cannot detect if the mutex is actually used - /// for waiting before a critical section. + /// * This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// * This lint has a false positive that warns without considering the case + /// where `Mutex` is used together with `Condvar`. /// /// ### Example /// ```no_run @@ -48,14 +51,23 @@ declare_clippy_lint! { /// Checks for usage of `Mutex` where `X` is an integral /// type. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using a mutex just to make access to a plain integer /// sequential is /// shooting flies with cannons. `std::sync::atomic::AtomicUsize` is leaner and faster. /// + /// On the other hand, `Mutex`es are, in general, easier to + /// verify correctness. An atomic does not behave the same as + /// an equivalent mutex. See [this issue](https://github.com/rust-lang/rust-clippy/issues/4295)'s + /// commentary for more details. + /// /// ### Known problems - /// This lint cannot detect if the mutex is actually used - /// for waiting before a critical section. + /// * This lint cannot detect if the mutex is actually used + /// for waiting before a critical section. + /// * This lint has a false positive that warns without considering the case + /// where `Mutex` is used together with `Condvar`. + /// * This lint suggest using `AtomicU64` instead of `Mutex`, but + /// `AtomicU64` is not available on some 32-bit platforms. /// /// ### Example /// ```no_run @@ -70,7 +82,7 @@ declare_clippy_lint! { /// ``` #[clippy::version = "pre 1.29.0"] pub MUTEX_INTEGER, - nursery, + restriction, "using a mutex for an integer type" } @@ -108,7 +120,7 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { UintTy::U32 => Some("AtomicU32"), UintTy::U64 => Some("AtomicU64"), UintTy::Usize => Some("AtomicUsize"), - // There's no `AtomicU128`. + // `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069 UintTy::U128 => None, } }, @@ -119,7 +131,7 @@ fn get_atomic_name(ty: Ty<'_>) -> Option<&'static str> { IntTy::I32 => Some("AtomicI32"), IntTy::I64 => Some("AtomicI64"), IntTy::Isize => Some("AtomicIsize"), - // There's no `AtomicI128`. + // `AtomicU128` is unstable and only available on a few platforms: https://github.com/rust-lang/rust/issues/99069 IntTy::I128 => None, } }, From 8db9ecfd74732f4227421fd9bbf3c7704f594cbe Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Thu, 30 Jan 2025 16:29:25 +0100 Subject: [PATCH 28/57] New lint: `precedence_bits`, with recent additions to `precedence` Commit 25505302665a707bedee68ca1f3faf2a09f12c00 has extended the `precedence` lint to include bitmasking and shift operations. The lint is warn by default, and this generates many hits, especially in embedded or system code, where it is very idiomatic to use expressions such as `1 << 3 | 1 << 5` without parentheses. This commit splits the recent addition into a new lint, which is put into the "restriction" category, while the original one stays in "complexity", because mixing bitmasking and arithmetic operations is less typical. --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/precedence.rs | 68 +++++++++++++++++++++--------- tests/ui/precedence.fixed | 8 ++-- tests/ui/precedence.stderr | 26 +----------- tests/ui/precedence_bits.fixed | 35 +++++++++++++++ tests/ui/precedence_bits.rs | 35 +++++++++++++++ tests/ui/precedence_bits.stderr | 29 +++++++++++++ 8 files changed, 153 insertions(+), 50 deletions(-) create mode 100644 tests/ui/precedence_bits.fixed create mode 100644 tests/ui/precedence_bits.rs create mode 100644 tests/ui/precedence_bits.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index bc42c07224e1..e3352ee07838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5954,6 +5954,7 @@ Released 2018-09-13 [`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters [`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma [`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence +[`precedence_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence_bits [`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl [`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal [`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 86bcf8edd578..179502abcc13 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -626,6 +626,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::permissions_set_readonly_false::PERMISSIONS_SET_READONLY_FALSE_INFO, crate::pointers_in_nomem_asm_block::POINTERS_IN_NOMEM_ASM_BLOCK_INFO, crate::precedence::PRECEDENCE_INFO, + crate::precedence::PRECEDENCE_BITS_INFO, crate::ptr::CMP_NULL_INFO, crate::ptr::INVALID_NULL_PTR_USAGE_INFO, crate::ptr::MUT_FROM_REF_INFO, diff --git a/clippy_lints/src/precedence.rs b/clippy_lints/src/precedence.rs index 421b2b747555..ec6835db897e 100644 --- a/clippy_lints/src/precedence.rs +++ b/clippy_lints/src/precedence.rs @@ -3,17 +3,14 @@ use clippy_utils::source::snippet_with_applicability; use rustc_ast::ast::BinOpKind::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; use rustc_ast::ast::{BinOpKind, Expr, ExprKind}; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_lint::{EarlyContext, EarlyLintPass, Lint}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; declare_clippy_lint! { /// ### What it does - /// Checks for operations where precedence may be unclear - /// and suggests to add parentheses. Currently it catches the following: - /// * mixed usage of arithmetic and bit shifting/combining operators without - /// parentheses - /// * mixed usage of bitmasking and bit shifting operators without parentheses + /// Checks for operations where precedence may be unclear and suggests to add parentheses. + /// It catches a mixed usage of arithmetic and bit shifting/combining operators without parentheses /// /// ### Why is this bad? /// Not everyone knows the precedence of those operators by @@ -21,15 +18,32 @@ declare_clippy_lint! { /// code. /// /// ### Example - /// * `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 - /// * `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2 + /// `1 << 2 + 3` equals 32, while `(1 << 2) + 3` equals 7 #[clippy::version = "pre 1.29.0"] pub PRECEDENCE, complexity, "operations where precedence may be unclear" } -declare_lint_pass!(Precedence => [PRECEDENCE]); +declare_clippy_lint! { + /// ### What it does + /// Checks for bit shifting operations combined with bit masking/combining operators + /// and suggest using parentheses. + /// + /// ### Why restrict this? + /// Not everyone knows the precedence of those operators by + /// heart, so expressions like these may trip others trying to reason about the + /// code. + /// + /// ### Example + /// `0x2345 & 0xF000 >> 12` equals 5, while `(0x2345 & 0xF000) >> 12` equals 2 + #[clippy::version = "1.86.0"] + pub PRECEDENCE_BITS, + restriction, + "operations mixing bit shifting with bit combining/masking" +} + +declare_lint_pass!(Precedence => [PRECEDENCE, PRECEDENCE_BITS]); impl EarlyLintPass for Precedence { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { @@ -38,10 +52,10 @@ impl EarlyLintPass for Precedence { } if let ExprKind::Binary(Spanned { node: op, .. }, ref left, ref right) = expr.kind { - let span_sugg = |expr: &Expr, sugg, appl| { + let span_sugg = |lint: &'static Lint, expr: &Expr, sugg, appl| { span_lint_and_sugg( cx, - PRECEDENCE, + lint, expr.span, "operator precedence might not be obvious", "consider parenthesizing your expression", @@ -57,37 +71,41 @@ impl EarlyLintPass for Precedence { match (op, get_bin_opt(left), get_bin_opt(right)) { ( BitAnd | BitOr | BitXor, - Some(Shl | Shr | Add | Div | Mul | Rem | Sub), - Some(Shl | Shr | Add | Div | Mul | Rem | Sub), + Some(left_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), + Some(right_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), ) - | (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), Some(Add | Div | Mul | Rem | Sub)) => { + | ( + Shl | Shr, + Some(left_op @ (Add | Div | Mul | Rem | Sub)), + Some(right_op @ (Add | Div | Mul | Rem | Sub)), + ) => { let sugg = format!( "({}) {} ({})", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, left_op, right_op]), expr, sugg, applicability); }, - (BitAnd | BitOr | BitXor, Some(Shl | Shr | Add | Div | Mul | Rem | Sub), _) - | (Shl | Shr, Some(Add | Div | Mul | Rem | Sub), _) => { + (BitAnd | BitOr | BitXor, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub)), _) + | (Shl | Shr, Some(side_op @ (Add | Div | Mul | Rem | Sub)), _) => { let sugg = format!( "({}) {} {}", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability); }, - (BitAnd | BitOr | BitXor, _, Some(Shl | Shr | Add | Div | Mul | Rem | Sub)) - | (Shl | Shr, _, Some(Add | Div | Mul | Rem | Sub)) => { + (BitAnd | BitOr | BitXor, _, Some(side_op @ (Shl | Shr | Add | Div | Mul | Rem | Sub))) + | (Shl | Shr, _, Some(side_op @ (Add | Div | Mul | Rem | Sub))) => { let sugg = format!( "{} {} ({})", snippet_with_applicability(cx, left.span, "..", &mut applicability), op.as_str(), snippet_with_applicability(cx, right.span, "..", &mut applicability) ); - span_sugg(expr, sugg, applicability); + span_sugg(lint_for(&[op, side_op]), expr, sugg, applicability); }, _ => (), } @@ -106,3 +124,11 @@ fn get_bin_opt(expr: &Expr) -> Option { fn is_bit_op(op: BinOpKind) -> bool { matches!(op, BitXor | BitAnd | BitOr | Shl | Shr) } + +fn lint_for(ops: &[BinOpKind]) -> &'static Lint { + if ops.iter().all(|op| is_bit_op(*op)) { + PRECEDENCE_BITS + } else { + PRECEDENCE + } +} diff --git a/tests/ui/precedence.fixed b/tests/ui/precedence.fixed index 9864dd2550b6..52144a18bac0 100644 --- a/tests/ui/precedence.fixed +++ b/tests/ui/precedence.fixed @@ -20,10 +20,10 @@ fn main() { 1 ^ (1 - 1); 3 | (2 - 1); 3 & (5 - 2); - 0x0F00 & (0x00F0 << 4); - 0x0F00 & (0xF000 >> 4); - (0x0F00 << 1) ^ 3; - (0x0F00 << 1) | 2; + 0x0F00 & 0x00F0 << 4; + 0x0F00 & 0xF000 >> 4; + 0x0F00 << 1 ^ 3; + 0x0F00 << 1 | 2; let b = 3; trip!(b * 8); diff --git a/tests/ui/precedence.stderr b/tests/ui/precedence.stderr index 329422cb8a69..68ad5cb4829a 100644 --- a/tests/ui/precedence.stderr +++ b/tests/ui/precedence.stderr @@ -43,29 +43,5 @@ error: operator precedence might not be obvious LL | 3 & 5 - 2; | ^^^^^^^^^ help: consider parenthesizing your expression: `3 & (5 - 2)` -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:23:5 - | -LL | 0x0F00 & 0x00F0 << 4; - | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:24:5 - | -LL | 0x0F00 & 0xF000 >> 4; - | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:25:5 - | -LL | 0x0F00 << 1 ^ 3; - | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3` - -error: operator precedence might not be obvious - --> tests/ui/precedence.rs:26:5 - | -LL | 0x0F00 << 1 | 2; - | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2` - -error: aborting due to 11 previous errors +error: aborting due to 7 previous errors diff --git a/tests/ui/precedence_bits.fixed b/tests/ui/precedence_bits.fixed new file mode 100644 index 000000000000..82fea0d14e43 --- /dev/null +++ b/tests/ui/precedence_bits.fixed @@ -0,0 +1,35 @@ +#![warn(clippy::precedence_bits)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::precedence +)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + 0x0F00 & (0x00F0 << 4); + 0x0F00 & (0xF000 >> 4); + (0x0F00 << 1) ^ 3; + (0x0F00 << 1) | 2; + + let b = 3; + trip!(b * 8); +} diff --git a/tests/ui/precedence_bits.rs b/tests/ui/precedence_bits.rs new file mode 100644 index 000000000000..9b353308b6ee --- /dev/null +++ b/tests/ui/precedence_bits.rs @@ -0,0 +1,35 @@ +#![warn(clippy::precedence_bits)] +#![allow( + unused_must_use, + clippy::no_effect, + clippy::unnecessary_operation, + clippy::precedence +)] +#![allow(clippy::identity_op)] +#![allow(clippy::eq_op)] + +macro_rules! trip { + ($a:expr) => { + match $a & 0b1111_1111u8 { + 0 => println!("a is zero ({})", $a), + _ => println!("a is {}", $a), + } + }; +} + +fn main() { + 1 << 2 + 3; + 1 + 2 << 3; + 4 >> 1 + 1; + 1 + 3 >> 2; + 1 ^ 1 - 1; + 3 | 2 - 1; + 3 & 5 - 2; + 0x0F00 & 0x00F0 << 4; + 0x0F00 & 0xF000 >> 4; + 0x0F00 << 1 ^ 3; + 0x0F00 << 1 | 2; + + let b = 3; + trip!(b * 8); +} diff --git a/tests/ui/precedence_bits.stderr b/tests/ui/precedence_bits.stderr new file mode 100644 index 000000000000..f468186b363c --- /dev/null +++ b/tests/ui/precedence_bits.stderr @@ -0,0 +1,29 @@ +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:28:5 + | +LL | 0x0F00 & 0x00F0 << 4; + | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0x00F0 << 4)` + | + = note: `-D clippy::precedence-bits` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::precedence_bits)]` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:29:5 + | +LL | 0x0F00 & 0xF000 >> 4; + | ^^^^^^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `0x0F00 & (0xF000 >> 4)` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:30:5 + | +LL | 0x0F00 << 1 ^ 3; + | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) ^ 3` + +error: operator precedence might not be obvious + --> tests/ui/precedence_bits.rs:31:5 + | +LL | 0x0F00 << 1 | 2; + | ^^^^^^^^^^^^^^^ help: consider parenthesizing your expression: `(0x0F00 << 1) | 2` + +error: aborting due to 4 previous errors + From 73993387fda0418261f19435fd65284f7db05822 Mon Sep 17 00:00:00 2001 From: Lukas Markeffsky <@> Date: Mon, 27 Jan 2025 04:30:00 +0100 Subject: [PATCH 29/57] introduce `ty::Value` Co-authored-by: FedericoBruzzone --- clippy_lints/src/large_const_arrays.rs | 5 ++--- clippy_lints/src/large_stack_arrays.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 623b6b4fcc14..cabf10b7e0ea 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -56,9 +56,8 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && !item.span.from_expansion() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() - && let Some((ty::ValTree::Leaf(element_count), _)) = cx.tcx - .try_normalize_erasing_regions(cx.typing_env(), *cst).unwrap_or(*cst).try_to_valtree() - && let element_count = element_count.to_target_usize(cx.tcx) + && let Some(element_count) = cx.tcx + .try_normalize_erasing_regions(cx.typing_env(), *cst).unwrap_or(*cst).try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) { diff --git a/clippy_lints/src/large_stack_arrays.rs b/clippy_lints/src/large_stack_arrays.rs index 46d7df6995a9..6f5c5d6b3ea3 100644 --- a/clippy_lints/src/large_stack_arrays.rs +++ b/clippy_lints/src/large_stack_arrays.rs @@ -8,7 +8,7 @@ use clippy_utils::source::snippet; use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, ConstKind}; +use rustc_middle::ty; use rustc_session::impl_lint_pass; use rustc_span::{Span, sym}; @@ -81,8 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays { && let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind && !self.is_from_vec_macro(cx, expr.span) && let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind() - && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() - && let element_count = element_count.to_target_usize(cx.tcx) + && let Some(element_count) = cst.try_to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && !cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, node)| { matches!( From 33bb8afd088f4c23a8706c8f2a84aedb8bf5e248 Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 19 Jan 2025 22:53:56 +0000 Subject: [PATCH 30/57] Fix expand/collapse all on site, make highlightjs lazier --- util/gh-pages/index_template.html | 23 ++++---- util/gh-pages/script.js | 87 +++++++++++++++++++++---------- util/gh-pages/style.css | 8 +-- util/gh-pages/theme.js | 9 ++++ 4 files changed, 81 insertions(+), 46 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index deb0ef0b4996..a9b646280030 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -24,14 +24,16 @@ Otherwise, have a great day =^.^= {# #} {# #} {# #} + {# #} + {# #} + {# #} {# #} {# #} - {# #}
{# #} {# #}
{# #}
Theme
{# #} - {# #} {# #} {# #} {# #} @@ -39,11 +41,12 @@ Otherwise, have a great day =^.^= {# #} {# #} {# #}
{# #}
{# #} + {# #}
{# #} {# #}
{# #}
{# #} - {# #} - {# #}
{# #} @@ -145,13 +148,13 @@ Otherwise, have a great day =^.^= {% for lint in lints %}
{# #} {# #} -