From 417a600c30a5856edb5ea791b54c2d6fafd887c4 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 22 Jun 2022 15:28:28 +0000 Subject: [PATCH 01/42] Introduce opaque type to hidden type projection --- clippy_utils/src/qualify_min_const_fn.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 3bf75bcbee83..9690ad277717 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -252,6 +252,7 @@ fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &B } }, ProjectionElem::ConstantIndex { .. } + | ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } | ProjectionElem::Deref From a2c6252cd33d125a670be991dd17a11745bfbe72 Mon Sep 17 00:00:00 2001 From: Takayuki Maeda Date: Sun, 17 Jul 2022 04:09:20 +0900 Subject: [PATCH 02/42] avoid some `Symbol` to `String` conversions --- clippy_lints/src/format.rs | 2 +- clippy_lints/src/inherent_to_string.rs | 2 +- clippy_lints/src/methods/unnecessary_to_owned.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 3084c70589fa..0aa085fc71bf 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -82,7 +82,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { then { let is_new_string = match value.kind { ExprKind::Binary(..) => true, - ExprKind::MethodCall(path, ..) => path.ident.name.as_str() == "to_string", + ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string, _ => false, }; let sugg = if format_args.format_string_span.contains(value.span) { diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 39f68a8a1b48..694f646c707c 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -98,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for InherentToString { if_chain! { // Check if item is a method, called to_string and has a parameter 'self' if let ImplItemKind::Fn(ref signature, _) = impl_item.kind; - if impl_item.ident.name.as_str() == "to_string"; + if impl_item.ident.name == sym::to_string; let decl = &signature.decl; if decl.implicit_self.has_implicit_self(); if decl.inputs.len() == 1; diff --git a/clippy_lints/src/methods/unnecessary_to_owned.rs b/clippy_lints/src/methods/unnecessary_to_owned.rs index b4c6bfb31ed1..b3276f1394ed 100644 --- a/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -427,5 +427,5 @@ fn is_cow_into_owned(cx: &LateContext<'_>, method_name: Symbol, method_def_id: D /// Returns true if the named method is `ToString::to_string`. fn is_to_string(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool { - method_name.as_str() == "to_string" && is_diag_trait_item(cx, method_def_id, sym::ToString) + method_name == sym::to_string && is_diag_trait_item(cx, method_def_id, sym::ToString) } From f88a1399bb89f9c4f95fb4a1e6d9b5ab05f35201 Mon Sep 17 00:00:00 2001 From: Caio Date: Tue, 12 Jul 2022 15:36:59 -0300 Subject: [PATCH 03/42] Stabilize `let_chains` --- clippy_dev/src/lib.rs | 2 +- clippy_lints/src/lib.rs | 2 +- clippy_utils/src/lib.rs | 2 +- tests/ui/needless_late_init.fixed | 1 - tests/ui/needless_late_init.rs | 1 - tests/ui/needless_late_init.stderr | 32 +++++++++++++++--------------- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 81e807cf10c7..fe69284ab10e 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(let_chains)] +#![cfg_attr(bootstrap, feature(let_chains))] #![feature(let_else)] #![feature(once_cell)] #![feature(rustc_private)] diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 172fdf8c8526..884baaea0292 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -4,7 +4,7 @@ #![feature(control_flow_enum)] #![feature(drain_filter)] #![feature(iter_intersperse)] -#![feature(let_chains)] +#![cfg_attr(bootstrap, feature(let_chains))] #![feature(let_else)] #![feature(lint_reasons)] #![feature(never_type)] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 242d4315378e..0089e1b64006 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,7 +1,7 @@ #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(let_else)] -#![feature(let_chains)] +#![cfg_attr(bootstrap, feature(let_chains))] #![feature(lint_reasons)] #![feature(once_cell)] #![feature(rustc_private)] diff --git a/tests/ui/needless_late_init.fixed b/tests/ui/needless_late_init.fixed index fee8e3030b80..4c98e1827bdb 100644 --- a/tests/ui/needless_late_init.fixed +++ b/tests/ui/needless_late_init.fixed @@ -1,5 +1,4 @@ // run-rustfix -#![feature(let_chains)] #![allow( unused, clippy::assign_op_pattern, diff --git a/tests/ui/needless_late_init.rs b/tests/ui/needless_late_init.rs index 402d9f9ef7f8..25e1e0214fb4 100644 --- a/tests/ui/needless_late_init.rs +++ b/tests/ui/needless_late_init.rs @@ -1,5 +1,4 @@ // run-rustfix -#![feature(let_chains)] #![allow( unused, clippy::assign_op_pattern, diff --git a/tests/ui/needless_late_init.stderr b/tests/ui/needless_late_init.stderr index 313cdbbeba18..97f0f7019a9d 100644 --- a/tests/ui/needless_late_init.stderr +++ b/tests/ui/needless_late_init.stderr @@ -1,5 +1,5 @@ error: unneeded late initialization - --> $DIR/needless_late_init.rs:23:5 + --> $DIR/needless_late_init.rs:22:5 | LL | let a; | ^^^^^^ created here @@ -13,7 +13,7 @@ LL | let a = "zero"; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:26:5 + --> $DIR/needless_late_init.rs:25:5 | LL | let b; | ^^^^^^ created here @@ -27,7 +27,7 @@ LL | let b = 1; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:27:5 + --> $DIR/needless_late_init.rs:26:5 | LL | let c; | ^^^^^^ created here @@ -41,7 +41,7 @@ LL | let c = 2; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:31:5 + --> $DIR/needless_late_init.rs:30:5 | LL | let d: usize; | ^^^^^^^^^^^^^ created here @@ -54,7 +54,7 @@ LL | let d: usize = 1; | ~~~~~~~~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:34:5 + --> $DIR/needless_late_init.rs:33:5 | LL | let e; | ^^^^^^ created here @@ -67,7 +67,7 @@ LL | let e = format!("{}", d); | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:39:5 + --> $DIR/needless_late_init.rs:38:5 | LL | let a; | ^^^^^^ @@ -88,7 +88,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:48:5 + --> $DIR/needless_late_init.rs:47:5 | LL | let b; | ^^^^^^ @@ -109,7 +109,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:55:5 + --> $DIR/needless_late_init.rs:54:5 | LL | let d; | ^^^^^^ @@ -130,7 +130,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:63:5 + --> $DIR/needless_late_init.rs:62:5 | LL | let e; | ^^^^^^ @@ -151,7 +151,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:70:5 + --> $DIR/needless_late_init.rs:69:5 | LL | let f; | ^^^^^^ @@ -167,7 +167,7 @@ LL + 1 => "three", | error: unneeded late initialization - --> $DIR/needless_late_init.rs:76:5 + --> $DIR/needless_late_init.rs:75:5 | LL | let g: usize; | ^^^^^^^^^^^^^ @@ -187,7 +187,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:84:5 + --> $DIR/needless_late_init.rs:83:5 | LL | let x; | ^^^^^^ created here @@ -201,7 +201,7 @@ LL | let x = 1; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:88:5 + --> $DIR/needless_late_init.rs:87:5 | LL | let x; | ^^^^^^ created here @@ -215,7 +215,7 @@ LL | let x = SignificantDrop; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:92:5 + --> $DIR/needless_late_init.rs:91:5 | LL | let x; | ^^^^^^ created here @@ -229,7 +229,7 @@ LL | let x = SignificantDrop; | ~~~~~ error: unneeded late initialization - --> $DIR/needless_late_init.rs:111:5 + --> $DIR/needless_late_init.rs:110:5 | LL | let a; | ^^^^^^ @@ -250,7 +250,7 @@ LL | }; | + error: unneeded late initialization - --> $DIR/needless_late_init.rs:128:5 + --> $DIR/needless_late_init.rs:127:5 | LL | let a; | ^^^^^^ From 8cf39a8c1908709831ab27937a53a2d6d0278ff6 Mon Sep 17 00:00:00 2001 From: Ariel Uy Date: Sun, 10 Jul 2022 09:39:36 -0700 Subject: [PATCH 04/42] Fix `mismatching_type_param_order` false positive Previously was giving false positive when an impl had a nontrivial generic argument such as a tuple. Don't lint on these cases. --- .../src/mismatching_type_param_order.rs | 24 ++++++++++++------- tests/ui/mismatching_type_param_order.rs | 4 ++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index d466d54a6ba5..8e477cb7179d 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -18,6 +18,11 @@ declare_clippy_lint! { /// Naming type parameters inconsistently may cause you to refer to the /// wrong type parameter. /// + /// ### Limitations + /// This lint only applies to impl blocks with simple generic params, e.g. + /// `A`. If there is anything more complicated, such as a tuple, it will be + /// ignored. + /// /// ### Example /// ```rust /// struct Foo { @@ -53,14 +58,15 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch { if !generic_args.args.is_empty(); then { // get the name and span of the generic parameters in the Impl - let impl_params = generic_args.args.iter() - .filter_map(|p| + let mut impl_params = Vec::new(); + for p in generic_args.args.iter() { match p { GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) => - Some((path.segments[0].ident.to_string(), path.span)), - _ => None, - } - ); + impl_params.push((path.segments[0].ident.to_string(), path.span)), + GenericArg::Type(_) => return, + _ => (), + }; + } // find the type that the Impl is for // only lint on struct/enum/union for now @@ -83,8 +89,8 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch { type_param_names.iter().enumerate().map(|(i, param)| (param, i)).collect(); let type_name = segment.ident; - for (i, (impl_param_name, impl_param_span)) in impl_params.enumerate() { - if mismatch_param_name(i, &impl_param_name, &type_param_names_hashmap) { + for (i, (impl_param_name, impl_param_span)) in impl_params.iter().enumerate() { + if mismatch_param_name(i, impl_param_name, &type_param_names_hashmap) { let msg = format!("`{}` has a similarly named generic type parameter `{}` in its declaration, but in a different order", type_name, impl_param_name); let help = format!("try `{}`, or a name that does not conflict with `{}`'s generic params", @@ -92,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch { span_lint_and_help( cx, MISMATCHING_TYPE_PARAM_ORDER, - impl_param_span, + *impl_param_span, &msg, None, &help diff --git a/tests/ui/mismatching_type_param_order.rs b/tests/ui/mismatching_type_param_order.rs index 8f286c9304cc..8c0da84d8e97 100644 --- a/tests/ui/mismatching_type_param_order.rs +++ b/tests/ui/mismatching_type_param_order.rs @@ -57,4 +57,8 @@ fn main() { B: Copy, { } + + // if the types are complicated, do not lint + impl Foo<(K, V), B> {} + impl Foo<(K, V), A> {} } From 9ff7c911008fc74c8d6c76afe12d7e30e67f8d25 Mon Sep 17 00:00:00 2001 From: Ariel Uy Date: Sun, 10 Jul 2022 14:37:38 -0700 Subject: [PATCH 05/42] Add new lint `obfuscated_if_else` New lint suggests using `if .. else ..` instead of `.then_some(..).unwrap_or(..)`. --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_style.rs | 1 + clippy_lints/src/methods/mod.rs | 34 +++++++++++++++ .../src/methods/obfuscated_if_else.rs | 42 +++++++++++++++++++ tests/ui/obfuscated_if_else.fixed | 7 ++++ tests/ui/obfuscated_if_else.rs | 7 ++++ tests/ui/obfuscated_if_else.stderr | 10 +++++ 9 files changed, 104 insertions(+) create mode 100644 clippy_lints/src/methods/obfuscated_if_else.rs create mode 100644 tests/ui/obfuscated_if_else.fixed create mode 100644 tests/ui/obfuscated_if_else.rs create mode 100644 tests/ui/obfuscated_if_else.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b792736a6ae..00fce79ec063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3792,6 +3792,7 @@ Released 2018-09-13 [`nonsensical_open_options`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonsensical_open_options [`nonstandard_macro_braces`]: https://rust-lang.github.io/rust-clippy/master/index.html#nonstandard_macro_braces [`not_unsafe_ptr_arg_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#not_unsafe_ptr_arg_deref +[`obfuscated_if_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#obfuscated_if_else [`octal_escapes`]: https://rust-lang.github.io/rust-clippy/master/index.html#octal_escapes [`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect [`only_used_in_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#only_used_in_recursion diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index da26a3f01301..69387d560c91 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -188,6 +188,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(methods::NEEDLESS_SPLITN), LintId::of(methods::NEW_RET_NO_SELF), LintId::of(methods::NO_EFFECT_REPLACE), + LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), LintId::of(methods::OPTION_AS_REF_DEREF), LintId::of(methods::OPTION_FILTER_MAP), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index ceb8470657f7..24b921faf033 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -330,6 +330,7 @@ store.register_lints(&[ methods::NEEDLESS_SPLITN, methods::NEW_RET_NO_SELF, methods::NO_EFFECT_REPLACE, + methods::OBFUSCATED_IF_ELSE, methods::OK_EXPECT, methods::OPTION_AS_REF_DEREF, methods::OPTION_FILTER_MAP, diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 15a1bc569af2..e95bab1d0454 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -70,6 +70,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(methods::MANUAL_SATURATING_ARITHMETIC), LintId::of(methods::MAP_COLLECT_RESULT_UNIT), LintId::of(methods::NEW_RET_NO_SELF), + LintId::of(methods::OBFUSCATED_IF_ELSE), LintId::of(methods::OK_EXPECT), LintId::of(methods::OPTION_MAP_OR_NONE), LintId::of(methods::RESULT_MAP_OR_INTO_OPTION), diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 29ec6e042863..c252272e2540 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -46,6 +46,7 @@ mod map_unwrap_or; mod needless_option_as_deref; mod needless_option_take; mod no_effect_replace; +mod obfuscated_if_else; mod ok_expect; mod option_as_ref_deref; mod option_map_or_none; @@ -2259,6 +2260,35 @@ declare_clippy_lint! { "replace with no effect" } +declare_clippy_lint! { + /// ### What it does + /// Checks for usages of `.then_some(..).unwrap_or(..)` + /// + /// ### 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 + /// to account for similar patterns. + /// + /// ### Example + /// ```rust + /// let x = true; + /// x.then_some("a").unwrap_or("b"); + /// ``` + /// Use instead: + /// ```rust + /// let x = true; + /// if x { "a" } else { "b" }; + /// ``` + #[clippy::version = "1.64.0"] + pub OBFUSCATED_IF_ELSE, + style, + "use of `.then_some(..).unwrap_or(..)` can be written \ + more clearly with `if .. else ..`" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Option, @@ -2360,6 +2390,7 @@ impl_lint_pass!(Methods => [ IS_DIGIT_ASCII_RADIX, NEEDLESS_OPTION_TAKE, NO_EFFECT_REPLACE, + OBFUSCATED_IF_ELSE, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -2768,6 +2799,9 @@ impl Methods { Some(("map", [m_recv, m_arg], span)) => { option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span); }, + Some(("then_some", [t_recv, t_arg], _)) => { + obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg); + }, _ => {}, }, ("unwrap_or_else", [u_arg]) => match method_call(recv) { diff --git a/clippy_lints/src/methods/obfuscated_if_else.rs b/clippy_lints/src/methods/obfuscated_if_else.rs new file mode 100644 index 000000000000..4d7427b26621 --- /dev/null +++ b/clippy_lints/src/methods/obfuscated_if_else.rs @@ -0,0 +1,42 @@ +// run-rustfix + +use super::OBFUSCATED_IF_ELSE; +use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_with_applicability}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx hir::Expr<'_>, + then_recv: &'tcx hir::Expr<'_>, + then_arg: &'tcx hir::Expr<'_>, + unwrap_arg: &'tcx hir::Expr<'_>, +) { + // 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 sugg = format!( + "if {} {{ {} }} else {{ {} }}", + snippet_with_applicability(cx, then_recv.span, "..", &mut applicability), + snippet_with_applicability(cx, then_arg.span, "..", &mut applicability), + snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability) + ); + + span_lint_and_sugg( + cx, + OBFUSCATED_IF_ELSE, + expr.span, + "use of `.then_some(..).unwrap_or(..)` can be written \ + more clearly with `if .. else ..`", + "try", + sugg, + applicability, + ); + } +} diff --git a/tests/ui/obfuscated_if_else.fixed b/tests/ui/obfuscated_if_else.fixed new file mode 100644 index 000000000000..62d932c2c6b7 --- /dev/null +++ b/tests/ui/obfuscated_if_else.fixed @@ -0,0 +1,7 @@ +// run-rustfix + +#![warn(clippy::obfuscated_if_else)] + +fn main() { + if true { "a" } else { "b" }; +} diff --git a/tests/ui/obfuscated_if_else.rs b/tests/ui/obfuscated_if_else.rs new file mode 100644 index 000000000000..273be9092a74 --- /dev/null +++ b/tests/ui/obfuscated_if_else.rs @@ -0,0 +1,7 @@ +// run-rustfix + +#![warn(clippy::obfuscated_if_else)] + +fn main() { + true.then_some("a").unwrap_or("b"); +} diff --git a/tests/ui/obfuscated_if_else.stderr b/tests/ui/obfuscated_if_else.stderr new file mode 100644 index 000000000000..e4180c288693 --- /dev/null +++ b/tests/ui/obfuscated_if_else.stderr @@ -0,0 +1,10 @@ +error: use of `.then_some(..).unwrap_or(..)` can be written more clearly with `if .. else ..` + --> $DIR/obfuscated_if_else.rs:6:5 + | +LL | true.then_some("a").unwrap_or("b"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `if true { "a" } else { "b" }` + | + = note: `-D clippy::obfuscated-if-else` implied by `-D warnings` + +error: aborting due to previous error + From 5b3300dc29c8ccce3e34ac37d5b79e5ca1b5e4c1 Mon Sep 17 00:00:00 2001 From: "Samuel E. Moelius III" Date: Sun, 17 Jul 2022 18:10:01 -0400 Subject: [PATCH 06/42] Add `ui_cargo_toml_metadata` test --- Cargo.toml | 1 + tests/compile-test.rs | 41 ++++++++++++++++++- .../cargo_common_metadata/fail/Cargo.toml | 2 +- .../fail/src/main.stderr | 12 +++--- .../fail_publish/Cargo.toml | 2 +- .../fail_publish/src/main.stderr | 12 +++--- .../fail_publish_true/Cargo.toml | 2 +- .../fail_publish_true/src/main.stderr | 12 +++--- .../cargo_common_metadata/pass/Cargo.toml | 2 +- .../pass_publish_empty/Cargo.toml | 2 +- .../pass_publish_false/Cargo.toml | 2 +- .../fail_both_diff/Cargo.toml | 1 + .../fail_both_same/Cargo.toml | 1 + .../cargo_rust_version/fail_cargo/Cargo.toml | 1 + .../cargo_rust_version/fail_clippy/Cargo.toml | 1 + .../fail_file_attr/Cargo.toml | 1 + .../pass_both_same/Cargo.toml | 3 +- .../cargo_rust_version/pass_cargo/Cargo.toml | 3 +- .../cargo_rust_version/pass_clippy/Cargo.toml | 3 +- .../pass_file_attr/Cargo.toml | 3 +- .../warn_both_diff/Cargo.toml | 1 + .../ui-cargo/module_style/fail_mod/Cargo.toml | 3 +- .../module_style/fail_no_mod/Cargo.toml | 3 +- .../ui-cargo/module_style/pass_mod/Cargo.toml | 3 +- .../module_style/pass_no_mod/Cargo.toml | 3 +- .../multiple_config_files/no_warn/Cargo.toml | 1 + .../multiple_config_files/warn/Cargo.toml | 1 + .../multiple_crate_versions/pass/Cargo.toml | 2 +- 28 files changed, 90 insertions(+), 34 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4993925489f9..69deaca0b4b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ compiletest_rs = { version = "0.8", features = ["tmp"] } tester = "0.9" regex = "1.5" toml = "0.5" +walkdir = "2.3" # This is used by the `collect-metadata` alias. filetime = "0.2" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 3615d07154df..92ac1a2be561 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -433,7 +433,7 @@ fn rustfix_coverage_known_exceptions_accuracy() { let rs_path = Path::new("tests/ui").join(filename); assert!( rs_path.exists(), - "`{}` does not exists", + "`{}` does not exist", rs_path.strip_prefix(env!("CARGO_MANIFEST_DIR")).unwrap().display() ); let fixed_path = rs_path.with_extension("fixed"); @@ -445,6 +445,45 @@ fn rustfix_coverage_known_exceptions_accuracy() { } } +#[test] +fn ui_cargo_toml_metadata() { + let ui_cargo_path = Path::new("tests/ui-cargo"); + let cargo_common_metadata_path = ui_cargo_path.join("cargo_common_metadata"); + let publish_exceptions = + ["fail_publish", "fail_publish_true", "pass_publish_empty"].map(|path| cargo_common_metadata_path.join(path)); + + for entry in walkdir::WalkDir::new(ui_cargo_path) { + let entry = entry.unwrap(); + let path = entry.path(); + if path.file_name() != Some(OsStr::new("Cargo.toml")) { + continue; + } + + let toml = fs::read_to_string(path).unwrap().parse::().unwrap(); + + let package = toml.as_table().unwrap().get("package").unwrap().as_table().unwrap(); + + let name = package.get("name").unwrap().as_str().unwrap().replace('-', "_"); + assert!( + path.parent() + .unwrap() + .components() + .map(|component| component.as_os_str().to_string_lossy().replace('-', "_")) + .any(|s| *s == name) + || path.starts_with(&cargo_common_metadata_path), + "{:?} has incorrect package name", + path + ); + + let publish = package.get("publish").and_then(toml::Value::as_bool).unwrap_or(true); + assert!( + !publish || publish_exceptions.contains(&path.parent().unwrap().to_path_buf()), + "{:?} lacks `publish = false`", + path + ); + } +} + /// Restores an env var on drop #[must_use] struct VarGuard { diff --git a/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml index ae0a60329962..bc8e428f8595 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/fail/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_fail" version = "0.1.0" publish = false diff --git a/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr b/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr index 5e9aa8dc36a6..86953142befa 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr +++ b/tests/ui-cargo/cargo_common_metadata/fail/src/main.stderr @@ -1,16 +1,16 @@ -error: package `cargo_common_metadata` is missing `package.description` metadata +error: package `cargo_common_metadata_fail` is missing `package.description` metadata | = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` -error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata +error: package `cargo_common_metadata_fail` is missing `either package.license or package.license_file` metadata -error: package `cargo_common_metadata` is missing `package.repository` metadata +error: package `cargo_common_metadata_fail` is missing `package.repository` metadata -error: package `cargo_common_metadata` is missing `package.readme` metadata +error: package `cargo_common_metadata_fail` is missing `package.readme` metadata -error: package `cargo_common_metadata` is missing `package.keywords` metadata +error: package `cargo_common_metadata_fail` is missing `package.keywords` metadata -error: package `cargo_common_metadata` is missing `package.categories` metadata +error: package `cargo_common_metadata_fail` is missing `package.categories` metadata error: aborting due to 6 previous errors diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml index 7595696353cd..5005b83f59d1 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_fail_publish" version = "0.1.0" publish = ["some-registry-name"] diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr b/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr index 5e9aa8dc36a6..ac1b5e8e9034 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish/src/main.stderr @@ -1,16 +1,16 @@ -error: package `cargo_common_metadata` is missing `package.description` metadata +error: package `cargo_common_metadata_fail_publish` is missing `package.description` metadata | = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` -error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata +error: package `cargo_common_metadata_fail_publish` is missing `either package.license or package.license_file` metadata -error: package `cargo_common_metadata` is missing `package.repository` metadata +error: package `cargo_common_metadata_fail_publish` is missing `package.repository` metadata -error: package `cargo_common_metadata` is missing `package.readme` metadata +error: package `cargo_common_metadata_fail_publish` is missing `package.readme` metadata -error: package `cargo_common_metadata` is missing `package.keywords` metadata +error: package `cargo_common_metadata_fail_publish` is missing `package.keywords` metadata -error: package `cargo_common_metadata` is missing `package.categories` metadata +error: package `cargo_common_metadata_fail_publish` is missing `package.categories` metadata error: aborting due to 6 previous errors diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml index 7e5b88383ccc..51858eecd0a6 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_fail_publish_true" version = "0.1.0" publish = true diff --git a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr index 5e9aa8dc36a6..be32c0dc418f 100644 --- a/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr +++ b/tests/ui-cargo/cargo_common_metadata/fail_publish_true/src/main.stderr @@ -1,16 +1,16 @@ -error: package `cargo_common_metadata` is missing `package.description` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `package.description` metadata | = note: `-D clippy::cargo-common-metadata` implied by `-D warnings` -error: package `cargo_common_metadata` is missing `either package.license or package.license_file` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `either package.license or package.license_file` metadata -error: package `cargo_common_metadata` is missing `package.repository` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `package.repository` metadata -error: package `cargo_common_metadata` is missing `package.readme` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `package.readme` metadata -error: package `cargo_common_metadata` is missing `package.keywords` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `package.keywords` metadata -error: package `cargo_common_metadata` is missing `package.categories` metadata +error: package `cargo_common_metadata_fail_publish_true` is missing `package.categories` metadata error: aborting due to 6 previous errors diff --git a/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml index cb4774d43a2c..9f6e51fb4d9f 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/pass/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_pass" version = "0.1.0" publish = false description = "A test package for the cargo_common_metadata lint" diff --git a/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml index 0a879c99b5bd..828efee3a8f8 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/pass_publish_empty/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_pass_publish_empty" version = "0.1.0" publish = [] diff --git a/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml b/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml index ae0a60329962..45a5bf7c5745 100644 --- a/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml +++ b/tests/ui-cargo/cargo_common_metadata/pass_publish_false/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "cargo_common_metadata_pass_publish_false" version = "0.1.0" publish = false diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml index 73ec29c5803b..946d1b366f09 100644 --- a/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/fail_both_diff/Cargo.toml @@ -2,6 +2,7 @@ name = "fail-both-diff" version = "0.1.0" rust-version = "1.56" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml index 2d6d547e4fe3..46b92a1050e3 100644 --- a/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/fail_both_same/Cargo.toml @@ -2,6 +2,7 @@ name = "fail-both-same" version = "0.1.0" rust-version = "1.57.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml index 36a53bd829d9..189cc9f68dc4 100644 --- a/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/fail_cargo/Cargo.toml @@ -2,6 +2,7 @@ name = "fail-cargo" version = "0.1.0" rust-version = "1.56.1" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml index 9f644a1a39a5..bdb7f261d9e4 100644 --- a/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/fail_clippy/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "fail-clippy" version = "0.1.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml b/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml index 5380e993b293..84448ea41f64 100644 --- a/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/fail_file_attr/Cargo.toml @@ -2,6 +2,7 @@ name = "fail-file-attr" version = "0.1.0" rust-version = "1.13" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml index 1f9bd8f9a84e..809c0e74875b 100644 --- a/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/pass_both_same/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail-both-same" +name = "pass-both-same" version = "0.1.0" rust-version = "1.13.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml index 77538027c0f8..32d404f842cf 100644 --- a/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/pass_cargo/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail-cargo" +name = "pass-cargo" version = "0.1.0" rust-version = "1.13.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml index 9f644a1a39a5..cc937d6e6254 100644 --- a/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/pass_clippy/Cargo.toml @@ -1,6 +1,7 @@ [package] -name = "fail-clippy" +name = "pass-clippy" version = "0.1.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml b/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml index f0387cd90b80..8ef689880d41 100644 --- a/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/pass_file_attr/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail-file-attr" +name = "pass-file-attr" version = "0.1.0" rust-version = "1.59" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml b/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml index a19d5b33fe56..e9f94594f702 100644 --- a/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml +++ b/tests/ui-cargo/cargo_rust_version/warn_both_diff/Cargo.toml @@ -2,6 +2,7 @@ name = "warn-both-diff" version = "0.1.0" rust-version = "1.56.0" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/module_style/fail_mod/Cargo.toml b/tests/ui-cargo/module_style/fail_mod/Cargo.toml index 27b61c09fb48..b3d36a9fb645 100644 --- a/tests/ui-cargo/module_style/fail_mod/Cargo.toml +++ b/tests/ui-cargo/module_style/fail_mod/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail" +name = "fail-mod" version = "0.1.0" edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/module_style/fail_no_mod/Cargo.toml b/tests/ui-cargo/module_style/fail_no_mod/Cargo.toml index 27b61c09fb48..3610d13c1f30 100644 --- a/tests/ui-cargo/module_style/fail_no_mod/Cargo.toml +++ b/tests/ui-cargo/module_style/fail_no_mod/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail" +name = "fail-no-mod" version = "0.1.0" edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/module_style/pass_mod/Cargo.toml b/tests/ui-cargo/module_style/pass_mod/Cargo.toml index 27b61c09fb48..1c2991695bc0 100644 --- a/tests/ui-cargo/module_style/pass_mod/Cargo.toml +++ b/tests/ui-cargo/module_style/pass_mod/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "fail" +name = "pass-mod" version = "0.1.0" edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/module_style/pass_no_mod/Cargo.toml b/tests/ui-cargo/module_style/pass_no_mod/Cargo.toml index 3c0896dd2cda..4180aaf5185c 100644 --- a/tests/ui-cargo/module_style/pass_no_mod/Cargo.toml +++ b/tests/ui-cargo/module_style/pass_no_mod/Cargo.toml @@ -1,7 +1,8 @@ [package] -name = "pass" +name = "pass-no-mod" version = "0.1.0" edition = "2018" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml b/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml index 79c973cbfd2d..7eb56cc4e9d9 100644 --- a/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml +++ b/tests/ui-cargo/multiple_config_files/no_warn/Cargo.toml @@ -2,6 +2,7 @@ name = "no_warn" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/multiple_config_files/warn/Cargo.toml b/tests/ui-cargo/multiple_config_files/warn/Cargo.toml index 3d5c707579bc..b4847d070aab 100644 --- a/tests/ui-cargo/multiple_config_files/warn/Cargo.toml +++ b/tests/ui-cargo/multiple_config_files/warn/Cargo.toml @@ -2,6 +2,7 @@ name = "warn" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml index b4b49bb369ac..6c46571c5bf6 100644 --- a/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml +++ b/tests/ui-cargo/multiple_crate_versions/pass/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cargo_common_metadata" +name = "multiple_crate_versions" version = "0.1.0" publish = false From 7d4daaa8fa71337fc78b1b7799d048d51a3b3fcd Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Mon, 18 Jul 2022 09:39:37 +0200 Subject: [PATCH 07/42] Merge commit 'fdb84cbfd25908df5683f8f62388f663d9260e39' into clippyup --- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/clippy_bors.yml | 6 +- CHANGELOG.md | 175 ++++++++++- CONTRIBUTING.md | 7 +- book/README.md | 2 +- book/src/development/basics.md | 10 +- .../infrastructure/changelog_update.md | 3 + book/src/usage.md | 2 +- clippy_dev/src/dogfood.rs | 33 ++ clippy_dev/src/lib.rs | 1 + clippy_dev/src/main.rs | 20 +- clippy_dev/src/update_lints.rs | 2 +- clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/borrow_deref_ref.rs | 2 +- clippy_lints/src/casts/mod.rs | 2 +- clippy_lints/src/copies.rs | 64 +++- clippy_lints/src/crate_in_macro_def.rs | 2 +- clippy_lints/src/dereference.rs | 57 ++-- clippy_lints/src/derive.rs | 2 +- clippy_lints/src/drop_forget_ref.rs | 4 +- clippy_lints/src/duplicate_mod.rs | 26 +- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/entry.rs | 2 +- clippy_lints/src/equatable_if_let.rs | 4 +- clippy_lints/src/format_push_string.rs | 10 +- clippy_lints/src/inherent_impl.rs | 2 +- clippy_lints/src/invalid_utf8_in_unchecked.rs | 74 +++++ clippy_lints/src/large_enum_variant.rs | 2 +- clippy_lints/src/lib.register_all.rs | 2 +- clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_lints.rs | 4 + clippy_lints/src/lib.register_perf.rs | 1 - clippy_lints/src/lib.register_restriction.rs | 4 + clippy_lints/src/lib.rs | 4 + .../src/loops/explicit_counter_loop.rs | 9 +- clippy_lints/src/loops/manual_find.rs | 2 +- clippy_lints/src/loops/manual_flatten.rs | 16 +- .../src/loops/while_let_on_iterator.rs | 21 +- clippy_lints/src/manual_async_fn.rs | 4 +- clippy_lints/src/manual_non_exhaustive.rs | 2 +- clippy_lints/src/manual_rem_euclid.rs | 3 +- clippy_lints/src/matches/manual_map.rs | 2 +- .../src/matches/match_like_matches.rs | 10 +- clippy_lints/src/matches/match_same_arms.rs | 6 +- clippy_lints/src/matches/mod.rs | 4 +- .../matches/significant_drop_in_scrutinee.rs | 4 + clippy_lints/src/methods/filter_map.rs | 107 +++---- clippy_lints/src/methods/manual_str_repeat.rs | 2 +- clippy_lints/src/methods/mod.rs | 25 +- clippy_lints/src/methods/or_fun_call.rs | 22 +- .../src/methods/unnecessary_lazy_eval.rs | 7 +- clippy_lints/src/misc.rs | 2 +- .../src/mismatching_type_param_order.rs | 2 +- clippy_lints/src/new_without_default.rs | 12 +- clippy_lints/src/octal_escapes.rs | 2 +- clippy_lints/src/only_used_in_recursion.rs | 4 +- clippy_lints/src/option_if_let_else.rs | 2 +- clippy_lints/src/ptr.rs | 2 +- clippy_lints/src/question_mark.rs | 296 +++++++++++------- clippy_lints/src/regex.rs | 2 +- clippy_lints/src/shadow.rs | 14 +- clippy_lints/src/std_instead_of_core.rs | 134 ++++++++ clippy_lints/src/swap_ptr_to_ref.rs | 2 +- clippy_lints/src/trait_bounds.rs | 184 ++++++++--- clippy_lints/src/types/box_collection.rs | 27 +- .../src/undocumented_unsafe_blocks.rs | 18 +- clippy_lints/src/unit_types/let_unit_value.rs | 157 +++++++--- clippy_lints/src/unit_types/mod.rs | 8 +- clippy_lints/src/utils/internal_lints.rs | 8 +- .../internal_lints/metadata_collector.rs | 8 +- clippy_lints/src/write.rs | 2 +- clippy_utils/src/ast_utils.rs | 14 +- clippy_utils/src/hir_utils.rs | 10 +- clippy_utils/src/lib.rs | 64 +++- clippy_utils/src/msrvs.rs | 1 + clippy_utils/src/paths.rs | 1 + clippy_utils/src/source.rs | 2 +- clippy_utils/src/sugg.rs | 30 +- clippy_utils/src/ty.rs | 54 +++- clippy_utils/src/visitors.rs | 116 ++++++- lintcheck/Cargo.toml | 4 +- lintcheck/README.md | 2 +- lintcheck/src/main.rs | 4 +- rust-toolchain | 2 +- tests/compile-test.rs | 1 + tests/dogfood.rs | 14 +- tests/lint_message_convention.rs | 59 ++-- tests/ui-cargo/duplicate_mod/fail/src/d.rs | 1 + tests/ui-cargo/duplicate_mod/fail/src/main.rs | 3 +- .../duplicate_mod/fail/src/main.stderr | 22 +- tests/ui/auxiliary/macro_rules.rs | 5 + tests/ui/box_collection.rs | 16 +- tests/ui/box_collection.stderr | 56 +++- .../branches_sharing_code/false_positives.rs | 54 ++++ tests/ui/cast_size_32bit.stderr | 2 +- tests/ui/crashes/ice-9041.rs | 8 + tests/ui/crashes/ice-9041.stderr | 10 + tests/ui/equatable_if_let.fixed | 6 + tests/ui/equatable_if_let.rs | 6 + tests/ui/equatable_if_let.stderr | 22 +- tests/ui/explicit_auto_deref.fixed | 4 + tests/ui/explicit_auto_deref.rs | 4 + tests/ui/explicit_auto_deref.stderr | 8 +- tests/ui/if_let_mutex.rs | 2 +- .../ui/inconsistent_struct_constructor.fixed | 2 +- tests/ui/inconsistent_struct_constructor.rs | 2 +- tests/ui/invalid_utf8_in_unchecked.rs | 20 ++ tests/ui/invalid_utf8_in_unchecked.stderr | 22 ++ tests/ui/let_unit.fixed | 74 ++++- tests/ui/let_unit.rs | 74 ++++- tests/ui/let_unit.stderr | 24 +- tests/ui/manual_filter_map.fixed | 34 ++ tests/ui/manual_filter_map.rs | 38 +++ tests/ui/manual_filter_map.stderr | 120 ++++++- tests/ui/manual_find_map.fixed | 37 +++ tests/ui/manual_find_map.rs | 41 +++ tests/ui/manual_find_map.stderr | 136 +++++++- tests/ui/manual_flatten.rs | 16 + tests/ui/manual_flatten.stderr | 30 +- tests/ui/map_flatten_fixable.fixed | 31 +- tests/ui/map_flatten_fixable.rs | 32 +- tests/ui/map_flatten_fixable.stderr | 35 ++- tests/ui/match_expr_like_matches_macro.fixed | 6 + tests/ui/match_expr_like_matches_macro.rs | 12 + tests/ui/match_expr_like_matches_macro.stderr | 43 ++- tests/ui/needless_borrow.fixed | 12 + tests/ui/needless_borrow.rs | 12 + tests/ui/needless_match.fixed | 1 + tests/ui/needless_match.rs | 1 + tests/ui/needless_match.stderr | 4 +- tests/ui/new_without_default.rs | 14 + tests/ui/no_effect.rs | 1 - tests/ui/no_effect.stderr | 60 ++-- tests/ui/or_fun_call.fixed | 12 +- tests/ui/or_fun_call.stderr | 106 ++----- tests/ui/question_mark.fixed | 73 +++-- tests/ui/question_mark.rs | 77 +++-- tests/ui/question_mark.stderr | 52 +-- tests/ui/same_name_method.rs | 2 +- tests/ui/search_is_some_fixable_some.fixed | 30 ++ tests/ui/search_is_some_fixable_some.rs | 30 ++ tests/ui/search_is_some_fixable_some.stderr | 26 +- tests/ui/shadow.stderr | 14 +- tests/ui/significant_drop_in_scrutinee.rs | 7 + tests/ui/std_instead_of_core.rs | 39 +++ tests/ui/std_instead_of_core.stderr | 85 +++++ tests/ui/trait_duplication_in_bounds.rs | 111 +++++++ tests/ui/trait_duplication_in_bounds.stderr | 114 ++++++- .../transmutes_expressible_as_ptr_casts.fixed | 2 +- .../ui/transmutes_expressible_as_ptr_casts.rs | 2 +- tests/ui/type_repetition_in_bounds.rs | 12 + tests/ui/type_repetition_in_bounds.stderr | 18 +- tests/ui/undocumented_unsafe_blocks.rs | 9 +- tests/ui/undocumented_unsafe_blocks.stderr | 62 ++-- tests/ui/unnecessary_lazy_eval.fixed | 2 + tests/ui/unnecessary_lazy_eval.rs | 2 + tests/ui/unnecessary_lazy_eval.stderr | 76 +++-- tests/ui/while_let_on_iterator.fixed | 46 ++- tests/ui/while_let_on_iterator.rs | 46 ++- tests/ui/while_let_on_iterator.stderr | 78 +++-- tests/ui/wildcard_imports.fixed | 2 +- tests/ui/wildcard_imports.rs | 2 +- util/etc/vscode-tasks.json | 22 +- util/gh-pages/index.html | 7 +- util/gh-pages/script.js | 5 +- 165 files changed, 3427 insertions(+), 963 deletions(-) create mode 100644 clippy_dev/src/dogfood.rs create mode 100644 clippy_lints/src/invalid_utf8_in_unchecked.rs create mode 100644 clippy_lints/src/std_instead_of_core.rs create mode 100644 tests/ui/crashes/ice-9041.rs create mode 100644 tests/ui/crashes/ice-9041.stderr create mode 100644 tests/ui/invalid_utf8_in_unchecked.rs create mode 100644 tests/ui/invalid_utf8_in_unchecked.stderr create mode 100644 tests/ui/std_instead_of_core.rs create mode 100644 tests/ui/std_instead_of_core.stderr diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 780ee9d63dfd..9e49f60892d2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,11 +3,15 @@ Thank you for making Clippy better! We're collecting our changelog from pull request descriptions. If your PR only includes internal changes, you can just write `changelog: none`. Otherwise, please write a short comment -explaining your change. Also, it's helpful for us that -the lint name is put into brackets `[]` and backticks `` ` ` ``, -e.g. ``[`lint_name`]``. +explaining your change. -If your PR fixes an issue, you can add "fixes #issue_number" into this +It's also helpful for us that the lint name is put within backticks (`` ` ` ``), +and then encapsulated by square brackets (`[]`), for example: +``` +changelog: [`lint_name`]: your change +``` + +If your PR fixes an issue, you can add `fixes #issue_number` into this PR description. This way the issue will be automatically closed when your PR is merged. diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index b8ea424ef348..97453303cd6a 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -144,7 +144,7 @@ jobs: OS: ${{ runner.os }} metadata_collection: - needs: base + needs: changelog runs-on: ubuntu-latest steps: @@ -264,7 +264,7 @@ jobs: name: bors test finished if: github.event.pusher.name == 'bors' && success() runs-on: ubuntu-latest - needs: [changelog, base, integration_build, integration] + needs: [changelog, base, metadata_collection, integration_build, integration] steps: - name: Mark the job as successful @@ -274,7 +274,7 @@ jobs: name: bors test finished if: github.event.pusher.name == 'bors' && (failure() || cancelled()) runs-on: ubuntu-latest - needs: [changelog, base, integration_build, integration] + needs: [changelog, base, metadata_collection, integration_build, integration] steps: - name: Mark the job as a failure diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bc93c1cb42c..920d397add71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,173 @@ # Changelog All notable changes to this project will be documented in this file. -See [Changelog Update](doc/changelog_update.md) if you want to update this +See [Changelog Update](book/src/development/infrastructure/changelog_update.md) if you want to update this document. ## Unreleased / In Rust Nightly -[d0cf3481...master](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...master) +[7c21f91b...master](https://github.com/rust-lang/rust-clippy/compare/7c21f91b...master) + +## Rust 1.62 + +Current stable, released 2022-06-30 + +[d0cf3481...7c21f91b](https://github.com/rust-lang/rust-clippy/compare/d0cf3481...7c21f91b) + +### New Lints + +* [`large_include_file`] + [#8727](https://github.com/rust-lang/rust-clippy/pull/8727) +* [`cast_abs_to_unsigned`] + [#8635](https://github.com/rust-lang/rust-clippy/pull/8635) +* [`err_expect`] + [#8606](https://github.com/rust-lang/rust-clippy/pull/8606) +* [`unnecessary_owned_empty_strings`] + [#8660](https://github.com/rust-lang/rust-clippy/pull/8660) +* [`empty_structs_with_brackets`] + [#8594](https://github.com/rust-lang/rust-clippy/pull/8594) +* [`crate_in_macro_def`] + [#8576](https://github.com/rust-lang/rust-clippy/pull/8576) +* [`needless_option_take`] + [#8665](https://github.com/rust-lang/rust-clippy/pull/8665) +* [`bytes_count_to_len`] + [#8711](https://github.com/rust-lang/rust-clippy/pull/8711) +* [`is_digit_ascii_radix`] + [#8624](https://github.com/rust-lang/rust-clippy/pull/8624) +* [`await_holding_invalid_type`] + [#8707](https://github.com/rust-lang/rust-clippy/pull/8707) +* [`trim_split_whitespace`] + [#8575](https://github.com/rust-lang/rust-clippy/pull/8575) +* [`pub_use`] + [#8670](https://github.com/rust-lang/rust-clippy/pull/8670) +* [`format_push_string`] + [#8626](https://github.com/rust-lang/rust-clippy/pull/8626) +* [`empty_drop`] + [#8571](https://github.com/rust-lang/rust-clippy/pull/8571) +* [`drop_non_drop`] + [#8630](https://github.com/rust-lang/rust-clippy/pull/8630) +* [`forget_non_drop`] + [#8630](https://github.com/rust-lang/rust-clippy/pull/8630) + +### Moves and Deprecations + +* Move [`only_used_in_recursion`] to `nursery` (now allow-by-default) + [#8783](https://github.com/rust-lang/rust-clippy/pull/8783) +* Move [`stable_sort_primitive`] to `pedantic` (now allow-by-default) + [#8716](https://github.com/rust-lang/rust-clippy/pull/8716) + +### Enhancements + +* Remove overlap between [`manual_split_once`] and [`needless_splitn`] + [#8631](https://github.com/rust-lang/rust-clippy/pull/8631) +* [`map_identity`]: Now checks for needless `map_err` + [#8487](https://github.com/rust-lang/rust-clippy/pull/8487) +* [`extra_unused_lifetimes`]: Now checks for impl lifetimes + [#8737](https://github.com/rust-lang/rust-clippy/pull/8737) +* [`cast_possible_truncation`]: Now catches more cases with larger shift or divide operations + [#8687](https://github.com/rust-lang/rust-clippy/pull/8687) +* [`identity_op`]: Now checks for modulo expressions + [#8519](https://github.com/rust-lang/rust-clippy/pull/8519) +* [`panic`]: No longer lint in constant context + [#8592](https://github.com/rust-lang/rust-clippy/pull/8592) +* [`manual_split_once`]: Now lints manual iteration of `splitn` + [#8717](https://github.com/rust-lang/rust-clippy/pull/8717) +* [`self_named_module_files`], [`mod_module_files`]: Now handle relative module paths + [#8611](https://github.com/rust-lang/rust-clippy/pull/8611) +* [`unsound_collection_transmute`]: Now has better size and alignment checks + [#8648](https://github.com/rust-lang/rust-clippy/pull/8648) +* [`unnested_or_patterns`]: Ignore cases, where the suggestion would be longer + [#8619](https://github.com/rust-lang/rust-clippy/pull/8619) + +### False Positive Fixes + +* [`rest_pat_in_fully_bound_structs`]: Now ignores structs marked with `#[non_exhaustive]` + [#8690](https://github.com/rust-lang/rust-clippy/pull/8690) +* [`needless_late_init`]: No longer lints `if let` statements, `let mut` bindings or instances that + changes the drop order significantly + [#8617](https://github.com/rust-lang/rust-clippy/pull/8617) +* [`unnecessary_cast`]: No longer lints to casts to aliased or non-primitive types + [#8596](https://github.com/rust-lang/rust-clippy/pull/8596) +* [`init_numbered_fields`]: No longer lints type aliases + [#8780](https://github.com/rust-lang/rust-clippy/pull/8780) +* [`needless_option_as_deref`]: No longer lints for `as_deref_mut` on `Option` values that can't be moved + [#8646](https://github.com/rust-lang/rust-clippy/pull/8646) +* [`mistyped_literal_suffixes`]: Now ignores float literals without an exponent + [#8742](https://github.com/rust-lang/rust-clippy/pull/8742) +* [`undocumented_unsafe_blocks`]: Now ignores unsafe blocks from proc-macros and works better for sub-expressions + [#8450](https://github.com/rust-lang/rust-clippy/pull/8450) +* [`same_functions_in_if_condition`]: Now allows different constants, even if they have the same value + [#8673](https://github.com/rust-lang/rust-clippy/pull/8673) +* [`needless_match`]: Now checks for more complex types and ignores type coercion + [#8549](https://github.com/rust-lang/rust-clippy/pull/8549) +* [`assertions_on_constants`]: Now ignores constants from `cfg!` macros + [#8614](https://github.com/rust-lang/rust-clippy/pull/8614) +* [`indexing_slicing`]: Fix false positives with constant indices in + [#8588](https://github.com/rust-lang/rust-clippy/pull/8588) +* [`iter_with_drain`]: Now ignores iterator references + [#8668](https://github.com/rust-lang/rust-clippy/pull/8668) +* [`useless_attribute`]: Now allows [`redundant_pub_crate`] on `use` items + [#8743](https://github.com/rust-lang/rust-clippy/pull/8743) +* [`cast_ptr_alignment`]: Now ignores expressions, when used for unaligned reads and writes + [#8632](https://github.com/rust-lang/rust-clippy/pull/8632) +* [`wrong_self_convention`]: Now allows `&mut self` and no self as arguments for `is_*` methods + [#8738](https://github.com/rust-lang/rust-clippy/pull/8738) +* [`mut_from_ref`]: Only lint in unsafe code + [#8647](https://github.com/rust-lang/rust-clippy/pull/8647) +* [`redundant_pub_crate`]: Now allows macro exports + [#8736](https://github.com/rust-lang/rust-clippy/pull/8736) +* [`needless_match`]: Ignores cases where the else block expression is different + [#8700](https://github.com/rust-lang/rust-clippy/pull/8700) +* [`transmute_int_to_char`]: Now allows transmutations in `const` code + [#8610](https://github.com/rust-lang/rust-clippy/pull/8610) +* [`manual_non_exhaustive`]: Ignores cases, where the enum value is used + [#8645](https://github.com/rust-lang/rust-clippy/pull/8645) +* [`redundant_closure`]: Now ignores coerced closure + [#8431](https://github.com/rust-lang/rust-clippy/pull/8431) +* [`identity_op`]: Is now ignored in cases where extra brackets would be needed + [#8730](https://github.com/rust-lang/rust-clippy/pull/8730) +* [`let_unit_value`]: Now ignores cases which are used for type inference + [#8563](https://github.com/rust-lang/rust-clippy/pull/8563) + +### Suggestion Fixes/Improvements + +* [`manual_split_once`]: Fixed incorrect suggestions for single result accesses + [#8631](https://github.com/rust-lang/rust-clippy/pull/8631) +* [`bytes_nth`]: Fix typos in the diagnostic message + [#8403](https://github.com/rust-lang/rust-clippy/pull/8403) +* [`mistyped_literal_suffixes`]: Now suggests the correct integer types + [#8742](https://github.com/rust-lang/rust-clippy/pull/8742) +* [`unnecessary_to_owned`]: Fixed suggestion based on the configured msrv + [#8692](https://github.com/rust-lang/rust-clippy/pull/8692) +* [`single_element_loop`]: Improve lint for Edition 2021 arrays + [#8616](https://github.com/rust-lang/rust-clippy/pull/8616) +* [`manual_bits`]: Now includes a cast for proper type conversion, when needed + [#8677](https://github.com/rust-lang/rust-clippy/pull/8677) +* [`option_map_unit_fn`], [`result_map_unit_fn`]: Fix some incorrect suggestions + [#8584](https://github.com/rust-lang/rust-clippy/pull/8584) +* [`collapsible_else_if`]: Add whitespace in suggestion + [#8729](https://github.com/rust-lang/rust-clippy/pull/8729) +* [`transmute_bytes_to_str`]: Now suggest `from_utf8_unchecked` in `const` context + [#8612](https://github.com/rust-lang/rust-clippy/pull/8612) +* [`map_clone`]: Improve message and suggestion based on the msrv + [#8688](https://github.com/rust-lang/rust-clippy/pull/8688) +* [`needless_late_init`]: Now shows the `let` statement where it was first initialized + [#8779](https://github.com/rust-lang/rust-clippy/pull/8779) + +### ICE Fixes + +* [`only_used_in_recursion`] + [#8691](https://github.com/rust-lang/rust-clippy/pull/8691) +* [`cast_slice_different_sizes`] + [#8720](https://github.com/rust-lang/rust-clippy/pull/8720) +* [`iter_overeager_cloned`] + [#8602](https://github.com/rust-lang/rust-clippy/pull/8602) +* [`undocumented_unsafe_blocks`] + [#8686](https://github.com/rust-lang/rust-clippy/pull/8686) ## Rust 1.61 -Current stable, released 2022-05-19 +Released 2022-05-19 [57b3c4b...d0cf3481](https://github.com/rust-lang/rust-clippy/compare/57b3c4b...d0cf3481) @@ -207,7 +364,7 @@ Released 2022-04-07 * [`needless_borrow`]: Prevent mutable borrows being moved and suggest removing the borrow on method calls [#8217](https://github.com/rust-lang/rust-clippy/pull/8217) -* [`chars_next_cmp`]: Correctly excapes the suggestion +* [`chars_next_cmp`]: Correctly escapes the suggestion [#8376](https://github.com/rust-lang/rust-clippy/pull/8376) * [`explicit_write`]: Add suggestions for `write!`s with format arguments [#8365](https://github.com/rust-lang/rust-clippy/pull/8365) @@ -1420,7 +1577,7 @@ Released 2021-03-25 * Add `cargo dev-lintcheck` tool to the Clippy Dev Tool [#6469](https://github.com/rust-lang/rust-clippy/pull/6469) -[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/doc/roadmap-2021.md +[Roadmap]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/proposals/roadmap-2021.md [Roadmap project page]: https://github.com/rust-lang/rust-clippy/projects/3 ## Rust 1.50 @@ -2368,7 +2525,7 @@ Released 2019-09-26 * [`inherent_to_string_shadow_display`] [#4259](https://github.com/rust-lang/rust-clippy/pull/4259) * [`type_repetition_in_bounds`] [#3766](https://github.com/rust-lang/rust-clippy/pull/3766) * [`try_err`] [#4222](https://github.com/rust-lang/rust-clippy/pull/4222) -* Move `{unnnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307) +* Move `{unnecessary,panicking}_unwrap` out of nursery [#4307](https://github.com/rust-lang/rust-clippy/pull/4307) * Extend the `use_self` lint to suggest uses of `Self::Variant` [#4308](https://github.com/rust-lang/rust-clippy/pull/4308) * Improve suggestion for needless return [#4262](https://github.com/rust-lang/rust-clippy/pull/4262) * Add auto-fixable suggestion for `let_unit` [#4337](https://github.com/rust-lang/rust-clippy/pull/4337) @@ -3269,12 +3426,13 @@ Released 2018-09-13 [`AsRef`]: https://doc.rust-lang.org/std/convert/trait.AsRef.html [configuration file]: ./rust-clippy#configuration [pull3665]: https://github.com/rust-lang/rust-clippy/pull/3665 -[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md +[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md [`README.md`]: https://github.com/rust-lang/rust-clippy/blob/master/README.md [`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons +[`alloc_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#alloc_instead_of_core [`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason [`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped @@ -3484,6 +3642,7 @@ Released 2018-09-13 [`invalid_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_ref [`invalid_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_regex [`invalid_upcast_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_upcast_comparisons +[`invalid_utf8_in_unchecked`]: https://rust-lang.github.io/rust-clippy/master/index.html#invalid_utf8_in_unchecked [`invisible_characters`]: https://rust-lang.github.io/rust-clippy/master/index.html#invisible_characters [`is_digit_ascii_radix`]: https://rust-lang.github.io/rust-clippy/master/index.html#is_digit_ascii_radix [`items_after_statements`]: https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements @@ -3743,6 +3902,8 @@ Released 2018-09-13 [`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next [`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 +[`std_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_core [`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string [`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add [`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e81e7ceedcb5..6e15133d267b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,6 @@ All contributors are expected to follow the [Rust Code of Conduct]. - [The Clippy book](#the-clippy-book) - [High level approach](#high-level-approach) - [Finding something to fix/improve](#finding-something-to-fiximprove) - - [Writing code](#writing-code) - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work) - [IntelliJ Rust](#intellij-rust) - [Rust Analyzer](#rust-analyzer) @@ -115,11 +114,11 @@ As of [#6869][6869], [`rust-analyzer`][ra_homepage] can understand that Clippy u using `extern crate` when `package.metadata.rust-analyzer.rustc_private` is set to `true` in Clippy's `Cargo.toml.` You will require a `nightly` toolchain with the `rustc-dev` component installed. Make sure that in the `rust-analyzer` configuration, you set -``` -{ "rust-analyzer.rustcSource": "discover" } +```json +{ "rust-analyzer.rustc.source": "discover" } ``` and -``` +```json { "rust-analyzer.updates.channel": "nightly" } ``` You should be able to see information on things like `Expr` or `EarlyContext` now if you hover them, also diff --git a/book/README.md b/book/README.md index b652194d0d13..6d67f80ff256 100644 --- a/book/README.md +++ b/book/README.md @@ -1,4 +1,4 @@ # Clippy Book This is the source for the Clippy Book. See the -[book](src/infrastructure/book.md) for more information. +[book](src/development/infrastructure/book.md) for more information. diff --git a/book/src/development/basics.md b/book/src/development/basics.md index 605897ff49cd..44ba6e32755e 100644 --- a/book/src/development/basics.md +++ b/book/src/development/basics.md @@ -4,8 +4,8 @@ This document explains the basics for hacking on Clippy. Besides others, this includes how to build and test Clippy. For a more in depth description on the codebase take a look at [Adding Lints] or [Common Tools]. -[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md -[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md +[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md +[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md - [Basics for hacking on Clippy](#basics-for-hacking-on-clippy) - [Get the Code](#get-the-code) @@ -59,7 +59,7 @@ cargo uitest # only run UI tests starting with `test_` TESTNAME="test_" cargo uitest # only run dogfood tests -cargo test --test dogfood +cargo dev dogfood ``` If the output of a [UI test] differs from the expected output, you can update @@ -91,10 +91,14 @@ cargo dev fmt cargo dev update_lints # create a new lint and register it cargo dev new_lint +# deprecate a lint and attempt to remove code relating to it +cargo dev deprecate # automatically formatting all code before each commit cargo dev setup git-hook # (experimental) Setup Clippy to work with IntelliJ-Rust cargo dev setup intellij +# runs the `dogfood` tests +cargo dev dogfood ``` More about intellij command usage and reasons diff --git a/book/src/development/infrastructure/changelog_update.md b/book/src/development/infrastructure/changelog_update.md index e560f4c6a3e5..80a47affe30d 100644 --- a/book/src/development/infrastructure/changelog_update.md +++ b/book/src/development/infrastructure/changelog_update.md @@ -95,6 +95,9 @@ As section headers, we use: Please also be sure to update the Beta/Unreleased sections at the top with the relevant commit ranges. +If you have the time, it would be appreciated if you double-check, that the +`#[clippy::version]` attributes for the added lints contains the correct version. + [changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md [forge]: https://forge.rust-lang.org/ [rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy diff --git a/book/src/usage.md b/book/src/usage.md index 5d858e0da468..61a90445d753 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -56,7 +56,7 @@ For more information on configuring lint levels, see the [rustc documentation]. Clippy has lint groups which are allow-by-default. This means, that you will have to enable the lints in those groups manually. -For a full list of all lints with their description and examples, please refere +For a full list of all lints with their description and examples, please refer to [Clippy's lint list]. The two most important allow-by-default groups are described below: diff --git a/clippy_dev/src/dogfood.rs b/clippy_dev/src/dogfood.rs new file mode 100644 index 000000000000..b69e9f649ec7 --- /dev/null +++ b/clippy_dev/src/dogfood.rs @@ -0,0 +1,33 @@ +use crate::clippy_project_root; +use std::process::Command; + +/// # Panics +/// +/// Panics if unable to run the dogfood test +pub fn dogfood(fix: bool, allow_dirty: bool, allow_staged: bool) { + let mut cmd = Command::new("cargo"); + + cmd.current_dir(clippy_project_root()) + .args(["test", "--test", "dogfood"]) + .args(["--features", "internal"]) + .args(["--", "dogfood_clippy"]); + + let mut dogfood_args = Vec::new(); + if fix { + dogfood_args.push("--fix"); + } + + if allow_dirty { + dogfood_args.push("--allow-dirty"); + } + + if allow_staged { + dogfood_args.push("--allow-staged"); + } + + cmd.env("__CLIPPY_DOGFOOD_ARGS", dogfood_args.join(" ")); + + let output = cmd.output().expect("failed to run command"); + + println!("{}", String::from_utf8_lossy(&output.stdout)); +} diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index fe69284ab10e..8536e2429926 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -11,6 +11,7 @@ extern crate rustc_lexer; use std::path::PathBuf; pub mod bless; +pub mod dogfood; pub mod fmt; pub mod lint; pub mod new_lint; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 243a901503f1..a29ba2d0c85e 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -3,7 +3,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; -use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; +use clippy_dev::{bless, dogfood, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; fn main() { @@ -13,6 +13,13 @@ fn main() { Some(("bless", matches)) => { bless::bless(matches.contains_id("ignore-timestamp")); }, + Some(("dogfood", matches)) => { + dogfood::dogfood( + matches.contains_id("fix"), + matches.contains_id("allow-dirty"), + matches.contains_id("allow-staged"), + ); + }, Some(("fmt", matches)) => { fmt::run(matches.contains_id("check"), matches.contains_id("verbose")); }, @@ -104,6 +111,17 @@ fn get_clap_config() -> ArgMatches { .long("ignore-timestamp") .help("Include files updated before clippy was built"), ), + Command::new("dogfood").about("Runs the dogfood test").args([ + Arg::new("fix").long("fix").help("Apply the suggestions when possible"), + Arg::new("allow-dirty") + .long("allow-dirty") + .help("Fix code even if the working directory has changes") + .requires("fix"), + Arg::new("allow-staged") + .long("allow-staged") + .help("Fix code even if the working directory has staged changes") + .requires("fix"), + ]), Command::new("fmt") .about("Run rustfmt on all projects and tests") .args([ diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 2e0659f42d7b..c089f4d8ce4b 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -553,7 +553,7 @@ fn replace_ident_like(contents: &str, replacements: &[(&str, &str)]) -> Option usize { diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index eee5f90d1788..1761360fb281 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -161,7 +161,7 @@ declare_clippy_lint! { /// baz().await; // Lint violation /// } /// ``` - #[clippy::version = "1.49.0"] + #[clippy::version = "1.62.0"] pub AWAIT_HOLDING_INVALID_TYPE, suspicious, "holding a type across an await point which is not allowed to be held as per the configuration" diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index 1582ec9ee5ce..937765b66147 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -22,7 +22,7 @@ declare_clippy_lint! { /// ``` /// let x = &12; /// let addr_x = &x as *const _ as usize; - /// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggerd. + /// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggered. /// // But if we fix it, assert will fail. /// assert_ne!(addr_x, addr_y); /// ``` diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index 02c2f30a4dd6..af3798a0cc8c 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -500,7 +500,7 @@ declare_clippy_lint! { /// let x: i32 = -42; /// let y: u32 = x.unsigned_abs(); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub CAST_ABS_TO_UNSIGNED, suspicious, "casting the result of `abs()` to an unsigned integer can panic" diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1deff9684a14..0e3d9317590f 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,13 +1,16 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; +use clippy_utils::ty::needs_ordered_drop; +use clippy_utils::visitors::for_each_expr; use clippy_utils::{ - eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, - search_same, ContainsName, HirEqInterExpr, SpanlessEq, + capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, + is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq, }; use core::iter; +use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::intravisit; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::walk_chain; @@ -214,7 +217,7 @@ fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[& fn lint_branches_sharing_code<'tcx>( cx: &LateContext<'tcx>, conds: &[&'tcx Expr<'_>], - blocks: &[&Block<'tcx>], + blocks: &[&'tcx Block<'_>], expr: &'tcx Expr<'_>, ) { // We only lint ifs with multiple blocks @@ -340,6 +343,21 @@ fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { } } +/// Checks if the statement modifies or moves any of the given locals. +fn modifies_any_local<'tcx>(cx: &LateContext<'tcx>, s: &'tcx Stmt<'_>, locals: &HirIdSet) -> bool { + for_each_expr(s, |e| { + if let Some(id) = path_to_local(e) + && locals.contains(&id) + && !capture_local_usage(cx, e).is_imm_ref() + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }) + .is_some() +} + /// Checks if the given statement should be considered equal to the statement in the same position /// for each block. fn eq_stmts( @@ -365,18 +383,52 @@ fn eq_stmts( .all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt))) } -fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq { +#[expect(clippy::too_many_lines)] +fn scan_block_for_eq<'tcx>( + cx: &LateContext<'tcx>, + conds: &[&'tcx Expr<'_>], + block: &'tcx Block<'_>, + blocks: &[&'tcx Block<'_>], +) -> BlockEq { let mut eq = SpanlessEq::new(cx); let mut eq = eq.inter_expr(); let mut moved_locals = Vec::new(); + let mut cond_locals = HirIdSet::default(); + for &cond in conds { + let _: Option = for_each_expr(cond, |e| { + if let Some(id) = path_to_local(e) { + cond_locals.insert(id); + } + ControlFlow::Continue(()) + }); + } + + let mut local_needs_ordered_drop = false; let start_end_eq = block .stmts .iter() .enumerate() - .find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)) + .find(|&(i, stmt)| { + if let StmtKind::Local(l) = stmt.kind + && needs_ordered_drop(cx, cx.typeck_results().node_type(l.hir_id)) + { + local_needs_ordered_drop = true; + return true; + } + modifies_any_local(cx, stmt, &cond_locals) + || !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals) + }) .map_or(block.stmts.len(), |(i, _)| i); + if local_needs_ordered_drop { + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; + } + // Walk backwards through the final expression/statements so long as their hashes are equal. Note // `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block // to match those in other blocks. e.g. If each block ends with the following the hash value will be diff --git a/clippy_lints/src/crate_in_macro_def.rs b/clippy_lints/src/crate_in_macro_def.rs index 9b8a481b6eab..f6ec8fe7edc1 100644 --- a/clippy_lints/src/crate_in_macro_def.rs +++ b/clippy_lints/src/crate_in_macro_def.rs @@ -43,7 +43,7 @@ declare_clippy_lint! { /// #[allow(clippy::crate_in_macro_def)] /// macro_rules! ok { ... crate::foo ... } /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub CRATE_IN_MACRO_DEF, suspicious, "using `crate` in a macro definition" diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 0f4a2f79ac5d..8c7cf7748be1 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -8,8 +8,8 @@ use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_ty, Visitor}; use rustc_hir::{ - self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Expr, ExprKind, GenericArg, HirId, ImplItem, - ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, + self as hir, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, + ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp, }; use rustc_infer::infer::TyCtxtInferExt; @@ -717,20 +717,36 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & Node::Expr(parent) if parent.span.ctxt() == ctxt => match parent.kind { ExprKind::Ret(_) => { - let output = cx - .tcx - .fn_sig(cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap())) - .skip_binder() - .output(); - Some(if !output.is_ref() { - Position::Other(precedence) - } else if output.has_placeholders() || output.has_opaque_types() { - Position::ReborrowStable(precedence) - } else { - Position::DerefStable(precedence) - }) + let owner_id = cx.tcx.hir().body_owner(cx.enclosing_body.unwrap()); + Some( + if let Node::Expr(Expr { + kind: ExprKind::Closure(&Closure { fn_decl, .. }), + .. + }) = cx.tcx.hir().get(owner_id) + { + match fn_decl.output { + FnRetTy::Return(ty) => binding_ty_auto_deref_stability(ty, precedence), + FnRetTy::DefaultReturn(_) => Position::Other(precedence), + } + } else { + let output = cx + .tcx + .fn_sig(cx.tcx.hir().local_def_id(owner_id)) + .skip_binder() + .output(); + if !output.is_ref() { + Position::Other(precedence) + } else if output.has_placeholders() || output.has_opaque_types() { + Position::ReborrowStable(precedence) + } else { + Position::DerefStable(precedence) + } + }, + ) + }, + ExprKind::Call(func, _) if func.hir_id == child_id => { + (child_id == e.hir_id).then_some(Position::Callee) }, - ExprKind::Call(func, _) if func.hir_id == child_id => (child_id == e.hir_id).then(|| Position::Callee), ExprKind::Call(func, args) => args .iter() .position(|arg| arg.hir_id == child_id) @@ -756,9 +772,14 @@ fn walk_parents<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (Position, & } else if let Some(trait_id) = cx.tcx.trait_of_item(id) && let arg_ty = cx.tcx.erase_regions(cx.typeck_results().expr_ty_adjusted(e)) && let ty::Ref(_, sub_ty, _) = *arg_ty.kind() - && let subs = cx.typeck_results().node_substs_opt(child_id).unwrap_or_else( - || cx.tcx.mk_substs([].iter()) - ) && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() { + && let subs = match cx + .typeck_results() + .node_substs_opt(parent.hir_id) + .and_then(|subs| subs.get(1..)) + { + Some(subs) => cx.tcx.mk_substs(subs.iter().copied()), + None => cx.tcx.mk_substs([].iter()), + } && let impl_ty = if cx.tcx.fn_sig(id).skip_binder().inputs()[0].is_ref() { // Trait methods taking `&self` sub_ty } else { diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 28f218a8e344..a982990e4186 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -189,7 +189,7 @@ declare_clippy_lint! { /// i_am_eq_too: Vec, /// } /// ``` - #[clippy::version = "1.62.0"] + #[clippy::version = "1.63.0"] pub DERIVE_PARTIAL_EQ_WITHOUT_EQ, style, "deriving `PartialEq` on a type that can implement `Eq`, without implementing `Eq`" diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 25014bfa1a5b..b35f0b8ca52d 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -116,7 +116,7 @@ declare_clippy_lint! { /// let x = Foo; /// std::mem::drop(x); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub DROP_NON_DROP, suspicious, "call to `std::mem::drop` with a value which does not implement `Drop`" @@ -136,7 +136,7 @@ declare_clippy_lint! { /// let x = Foo; /// std::mem::forget(x); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub FORGET_NON_DROP, suspicious, "call to `std::mem::forget` with a value which does not implement `Drop`" diff --git a/clippy_lints/src/duplicate_mod.rs b/clippy_lints/src/duplicate_mod.rs index 4f49bb879f50..e1eb3b6324c7 100644 --- a/clippy_lints/src/duplicate_mod.rs +++ b/clippy_lints/src/duplicate_mod.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_help; use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind}; use rustc_errors::MultiSpan; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext, Level}; +use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{FileName, Span}; use std::collections::BTreeMap; @@ -79,21 +79,29 @@ impl EarlyLintPass for DuplicateMod { } fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) { - for Modules { local_path, spans, lint_levels } in self.modules.values() { + for Modules { + local_path, + spans, + lint_levels, + } in self.modules.values() + { if spans.len() < 2 { continue; } // At this point the lint would be emitted assert_eq!(spans.len(), lint_levels.len()); - let spans: Vec<_> = spans.into_iter().zip(lint_levels).filter_map(|(span, lvl)|{ - if let Some(id) = lvl.get_expectation_id() { - cx.fulfill_expectation(id); - } + let spans: Vec<_> = spans + .iter() + .zip(lint_levels) + .filter_map(|(span, lvl)| { + if let Some(id) = lvl.get_expectation_id() { + cx.fulfill_expectation(id); + } - (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span) - }) - .collect(); + (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span) + }) + .collect(); if spans.len() < 2 { continue; diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index 325ae2356c14..ec063c0f777e 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -26,7 +26,7 @@ declare_clippy_lint! { /// ```rust /// struct S; /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub EMPTY_DROP, restriction, "empty `Drop` implementations" diff --git a/clippy_lints/src/entry.rs b/clippy_lints/src/entry.rs index 27743a0ebec7..4e3ae4c96141 100644 --- a/clippy_lints/src/entry.rs +++ b/clippy_lints/src/entry.rs @@ -650,7 +650,7 @@ fn find_insert_calls<'tcx>( let allow_insert_closure = s.allow_insert_closure; let is_single_insert = s.is_single_insert; let edits = s.edits; - s.can_use_entry.then(|| InsertSearchResults { + s.can_use_entry.then_some(InsertSearchResults { edits, allow_insert_closure, is_single_insert, diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index ef1216358dd9..fdfb821ac789 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -4,7 +4,8 @@ use clippy_utils::ty::implements_trait; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, Pat, PatKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -67,6 +68,7 @@ fn is_structural_partial_eq<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>, other: T impl<'tcx> LateLintPass<'tcx> for PatternEquality { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if_chain! { + if !in_external_macro(cx.sess(), expr.span); if let ExprKind::Let(let_expr) = expr.kind; if unary_pattern(let_expr.pat); let exp_ty = cx.typeck_results().expr_ty(let_expr.init); diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index ee15ae9f59ac..ebf5ab086dce 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -14,6 +14,12 @@ declare_clippy_lint! { /// ### Why is this bad? /// Introduces an extra, avoidable heap allocation. /// + /// ### Known problems + /// `format!` returns a `String` but `write!` returns a `Result`. + /// Thus you are forced to ignore the `Err` variant to achieve the same API. + /// + /// While using `write!` in the suggested way should never fail, this isn't necessarily clear to the programmer. + /// /// ### Example /// ```rust /// let mut s = String::new(); @@ -27,9 +33,9 @@ declare_clippy_lint! { /// let mut s = String::new(); /// let _ = write!(s, "0x{:X}", 1024); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub FORMAT_PUSH_STRING, - perf, + restriction, "`format!(..)` appended to existing `String`" } declare_lint_pass!(FormatPushString => [FORMAT_PUSH_STRING]); diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 6a031a627df9..c5abcc462545 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -127,7 +127,7 @@ fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { (!span.from_expansion() && impl_item.generics.params.is_empty() && !is_lint_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) - .then(|| span) + .then_some(span) } else { None } diff --git a/clippy_lints/src/invalid_utf8_in_unchecked.rs b/clippy_lints/src/invalid_utf8_in_unchecked.rs new file mode 100644 index 000000000000..e0a607f9a95b --- /dev/null +++ b/clippy_lints/src/invalid_utf8_in_unchecked.rs @@ -0,0 +1,74 @@ +use clippy_utils::diagnostics::span_lint; +use clippy_utils::{match_function_call, paths}; +use rustc_ast::{BorrowKind, LitKind}; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Spanned; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `std::str::from_utf8_unchecked` with an invalid UTF-8 literal + /// + /// ### Why is this bad? + /// Creating such a `str` would result in undefined behavior + /// + /// ### Example + /// ```rust + /// # #[allow(unused)] + /// unsafe { + /// std::str::from_utf8_unchecked(b"cl\x82ippy"); + /// } + /// ``` + #[clippy::version = "1.64.0"] + pub INVALID_UTF8_IN_UNCHECKED, + correctness, + "using a non UTF-8 literal in `std::std::from_utf8_unchecked`" +} +declare_lint_pass!(InvalidUtf8InUnchecked => [INVALID_UTF8_IN_UNCHECKED]); + +impl<'tcx> LateLintPass<'tcx> for InvalidUtf8InUnchecked { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if let Some([arg]) = match_function_call(cx, expr, &paths::STR_FROM_UTF8_UNCHECKED) { + match &arg.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => { + if let LitKind::ByteStr(bytes) = &lit + && std::str::from_utf8(bytes).is_err() + { + lint(cx, expr.span); + } + }, + ExprKind::AddrOf(BorrowKind::Ref, _, Expr { kind: ExprKind::Array(args), .. }) => { + let elements = args.iter().map(|e|{ + match &e.kind { + ExprKind::Lit(Spanned { node: lit, .. }) => match lit { + LitKind::Byte(b) => Some(*b), + #[allow(clippy::cast_possible_truncation)] + LitKind::Int(b, _) => Some(*b as u8), + _ => None + } + _ => None + } + }).collect::>>(); + + if let Some(elements) = elements + && std::str::from_utf8(&elements).is_err() + { + lint(cx, expr.span); + } + } + _ => {} + } + } + } +} + +fn lint(cx: &LateContext<'_>, span: Span) { + span_lint( + cx, + INVALID_UTF8_IN_UNCHECKED, + span, + "non UTF-8 literal in `std::str::from_utf8_unchecked`", + ); +} diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 9be057bcf901..c58df126d624 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { /// For types that implement `Copy`, the suggestion to `Box` a variant's /// data would require removing the trait impl. The types can of course /// still be `Clone`, but that is worse ergonomically. Depending on the - /// use case it may be possible to store the large data in an auxillary + /// use case it may be possible to store the large data in an auxiliary /// structure (e.g. Arena or ECS). /// /// The lint will ignore generic types if the layout depends on the diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 563ad891603a..9afc714b11ca 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -71,7 +71,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), LintId::of(format_impl::PRINT_IN_FORMAT_IMPL), LintId::of(format_impl::RECURSIVE_FORMAT_IMPL), - LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(formatting::POSSIBLE_MISSING_COMMA), LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING), LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING), @@ -92,6 +91,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS), LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY), LintId::of(int_plus_one::INT_PLUS_ONE), + LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED), LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(len_zero::COMPARISON_TO_EMPTY), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 7d5e65cb27a1..9975859c54fe 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -29,6 +29,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(infinite_iter::INFINITE_ITER), LintId::of(inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY), LintId::of(inline_fn_without_body::INLINE_FN_WITHOUT_BODY), + LintId::of(invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED), LintId::of(let_underscore::LET_UNDERSCORE_LOCK), LintId::of(literal_representation::MISTYPED_LITERAL_SUFFIXES), LintId::of(loops::ITER_NEXT_LOOP), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index d3c75f8b5191..91d27bf526d0 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -196,6 +196,7 @@ store.register_lints(&[ inline_fn_without_body::INLINE_FN_WITHOUT_BODY, int_plus_one::INT_PLUS_ONE, invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS, + invalid_utf8_in_unchecked::INVALID_UTF8_IN_UNCHECKED, items_after_statements::ITEMS_AFTER_STATEMENTS, iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR, large_const_arrays::LARGE_CONST_ARRAYS, @@ -496,6 +497,9 @@ store.register_lints(&[ size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT, slow_vector_initialization::SLOW_VECTOR_INITIALIZATION, stable_sort_primitive::STABLE_SORT_PRIMITIVE, + std_instead_of_core::ALLOC_INSTEAD_OF_CORE, + std_instead_of_core::STD_INSTEAD_OF_ALLOC, + std_instead_of_core::STD_INSTEAD_OF_CORE, strings::STRING_ADD, strings::STRING_ADD_ASSIGN, strings::STRING_FROM_UTF8_AS_BYTES, diff --git a/clippy_lints/src/lib.register_perf.rs b/clippy_lints/src/lib.register_perf.rs index 6bf519c24e84..e1b90acb93c2 100644 --- a/clippy_lints/src/lib.register_perf.rs +++ b/clippy_lints/src/lib.register_perf.rs @@ -7,7 +7,6 @@ store.register_group(true, "clippy::perf", Some("clippy_perf"), vec![ LintId::of(escape::BOXED_LOCAL), LintId::of(format_args::FORMAT_IN_FORMAT_ARGS), LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS), - LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(large_const_arrays::LARGE_CONST_ARRAYS), LintId::of(large_enum_variant::LARGE_ENUM_VARIANT), LintId::of(loops::MANUAL_MEMCPY), diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 970e9db4772c..43f1c892eb9b 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -21,6 +21,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(exhaustive_items::EXHAUSTIVE_STRUCTS), LintId::of(exit::EXIT), LintId::of(float_literal::LOSSY_FLOAT_LITERAL), + LintId::of(format_push_string::FORMAT_PUSH_STRING), LintId::of(if_then_some_else_none::IF_THEN_SOME_ELSE_NONE), LintId::of(implicit_return::IMPLICIT_RETURN), LintId::of(indexing_slicing::INDEXING_SLICING), @@ -65,6 +66,9 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(shadow::SHADOW_SAME), LintId::of(shadow::SHADOW_UNRELATED), LintId::of(single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES), + LintId::of(std_instead_of_core::ALLOC_INSTEAD_OF_CORE), + LintId::of(std_instead_of_core::STD_INSTEAD_OF_ALLOC), + LintId::of(std_instead_of_core::STD_INSTEAD_OF_CORE), LintId::of(strings::STRING_ADD), LintId::of(strings::STRING_SLICE), LintId::of(strings::STRING_TO_STRING), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 884baaea0292..1988c24578e0 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -255,6 +255,7 @@ mod init_numbered_fields; mod inline_fn_without_body; mod int_plus_one; mod invalid_upcast_comparisons; +mod invalid_utf8_in_unchecked; mod items_after_statements; mod iter_not_returning_iterator; mod large_const_arrays; @@ -364,6 +365,7 @@ mod single_component_path_imports; mod size_of_in_element_count; mod slow_vector_initialization; mod stable_sort_primitive; +mod std_instead_of_core; mod strings; mod strlen_on_c_strings; mod suspicious_operation_groupings; @@ -913,6 +915,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(move || Box::new(manual_retain::ManualRetain::new(msrv))); let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold))); + store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked)); + store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/explicit_counter_loop.rs b/clippy_lints/src/loops/explicit_counter_loop.rs index fc50e8addcce..8e3ab26a947f 100644 --- a/clippy_lints/src/loops/explicit_counter_loop.rs +++ b/clippy_lints/src/loops/explicit_counter_loop.rs @@ -34,7 +34,8 @@ pub(super) fn check<'tcx>( if let Some((name, ty, initializer)) = initialize_visitor.get_result(); if is_integer_const(cx, initializer, 0); then { - let mut applicability = Applicability::MachineApplicable; + let mut applicability = Applicability::MaybeIncorrect; + let span = expr.span.with_hi(arg.span.hi()); let int_name = match ty.map(Ty::kind) { // usize or inferred @@ -42,7 +43,7 @@ pub(super) fn check<'tcx>( span_lint_and_sugg( cx, EXPLICIT_COUNTER_LOOP, - expr.span.with_hi(arg.span.hi()), + span, &format!("the variable `{}` is used as a loop counter", name), "consider using", format!( @@ -63,11 +64,11 @@ pub(super) fn check<'tcx>( span_lint_and_then( cx, EXPLICIT_COUNTER_LOOP, - expr.span.with_hi(arg.span.hi()), + span, &format!("the variable `{}` is used as a loop counter", name), |diag| { diag.span_suggestion( - expr.span.with_hi(arg.span.hi()), + span, "consider using", format!( "for ({}, {}) in (0_{}..).zip({})", diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index 33736d6d4e65..215c83a7edf6 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -139,7 +139,7 @@ fn last_stmt_and_ret<'tcx>( if_chain! { // This should be the loop if let Some((node_hir, Node::Stmt(..))) = parent_iter.next(); - // This should be the funciton body + // This should be the function body if let Some((_, Node::Block(block))) = parent_iter.next(); if let Some((last_stmt, last_ret)) = extract(block); if last_stmt.hir_id == node_hir; diff --git a/clippy_lints/src/loops/manual_flatten.rs b/clippy_lints/src/loops/manual_flatten.rs index d276c9010599..1d6ddf4b99f7 100644 --- a/clippy_lints/src/loops/manual_flatten.rs +++ b/clippy_lints/src/loops/manual_flatten.rs @@ -51,22 +51,32 @@ pub(super) fn check<'tcx>( _ => "" }; + let sugg = format!("{arg_snippet}{copied}.flatten()"); + + // If suggestion is not a one-liner, it won't be shown inline within the error message. In that case, + // it will be shown in the extra `help` message at the end, which is why the first `help_msg` needs + // to refer to the correct relative position of the suggestion. + let help_msg = if sugg.contains('\n') { + "remove the `if let` statement in the for loop and then..." + } else { + "...and remove the `if let` statement in the for loop" + }; + span_lint_and_then( cx, MANUAL_FLATTEN, span, &msg, |diag| { - let sugg = format!("{}{}.flatten()", arg_snippet, copied); diag.span_suggestion( arg.span, "try", sugg, - Applicability::MaybeIncorrect, + applicability, ); diag.span_help( inner_expr.span, - "...and remove the `if let` statement in the for loop", + help_msg, ); } ); diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index b94bbd2bd417..e9e215e662f1 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -3,13 +3,15 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{ - get_enclosing_loop_or_closure, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used, + get_enclosing_loop_or_multi_call_closure, is_refutable, is_trait_method, match_def_path, paths, + visitors::is_res_used, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{Closure, def::Res, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; +use rustc_hir::{def::Res, Closure, Expr, ExprKind, HirId, Local, Mutability, PatKind, QPath, UnOp}; use rustc_lint::LateContext; +use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::ty::adjustment::Adjust; use rustc_span::{symbol::sym, Symbol}; @@ -249,6 +251,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & used_iter: bool, } impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { + type NestedFilter = OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if self.used_iter { return; @@ -283,6 +290,11 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & used_after: bool, } impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> { + type NestedFilter = OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + fn visit_local(&mut self, l: &'tcx Local<'_>) { if !self.after_loop { l.pat.each_binding_or_first(&mut |_, id, _, _| { @@ -320,10 +332,7 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & } } - if let Some(e) = get_enclosing_loop_or_closure(cx.tcx, loop_expr) { - // The iterator expression will be used on the next iteration (for loops), or on the next call (for - // closures) unless it is declared within the enclosing expression. TODO: Check for closures - // used where an `FnOnce` type is expected. + if let Some(e) = get_enclosing_loop_or_multi_call_closure(cx, loop_expr) { let local_id = match iter_expr.path { Res::Local(id) => id, _ => return true, diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index 93a34f452f6d..945880d21471 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -6,8 +6,8 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{ - AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, HirId, - IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, + AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, + HirId, IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; diff --git a/clippy_lints/src/manual_non_exhaustive.rs b/clippy_lints/src/manual_non_exhaustive.rs index 4278e98dc91f..2b04475c7a9d 100644 --- a/clippy_lints/src/manual_non_exhaustive.rs +++ b/clippy_lints/src/manual_non_exhaustive.rs @@ -161,7 +161,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum { (matches!(v.data, hir::VariantData::Unit(_)) && v.ident.as_str().starts_with('_') && is_doc_hidden(cx.tcx.hir().attrs(v.id))) - .then(|| (id, v.span)) + .then_some((id, v.span)) }); if let Some((id, span)) = iter.next() && iter.next().is_none() diff --git a/clippy_lints/src/manual_rem_euclid.rs b/clippy_lints/src/manual_rem_euclid.rs index 2ce9d0e77c1f..95cc6bdbd8ba 100644 --- a/clippy_lints/src/manual_rem_euclid.rs +++ b/clippy_lints/src/manual_rem_euclid.rs @@ -71,7 +71,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid { && let Some(const3) = check_for_unsigned_int_constant(cx, right) // Also ensures the const is nonzero since zero can't be a divisor && const1 == const2 && const2 == const3 - && let Some(hir_id) = path_to_local(expr3) { + && let Some(hir_id) = path_to_local(expr3) + && let Some(Node::Pat(_)) = cx.tcx.hir().find(hir_id) { // Apply only to params or locals with annotated types match cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { Some(Node::Param(..)) => (), diff --git a/clippy_lints/src/matches/manual_map.rs b/clippy_lints/src/matches/manual_map.rs index 542905a2d763..8f98b43b9e5c 100644 --- a/clippy_lints/src/matches/manual_map.rs +++ b/clippy_lints/src/matches/manual_map.rs @@ -105,7 +105,7 @@ fn check<'tcx>( // Determine which binding mode to use. let explicit_ref = some_pat.contains_explicit_ref_binding(); - let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability)); + let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then_some(ty_mutability)); let as_ref_str = match binding_ref { Some(Mutability::Mut) => ".as_mut()", diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index a68eec842abc..0da4833f1dfe 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -81,14 +81,14 @@ where if let Some((_, last_pat_opt, last_expr, _)) = iter.next_back(); let iter_without_last = iter.clone(); if let Some((first_attrs, _, first_expr, first_guard)) = iter.next(); - if let Some(b0) = find_bool_lit(&first_expr.kind, is_if_let); - if let Some(b1) = find_bool_lit(&last_expr.kind, is_if_let); + if let Some(b0) = find_bool_lit(&first_expr.kind); + if let Some(b1) = find_bool_lit(&last_expr.kind); if b0 != b1; if first_guard.is_none() || iter.len() == 0; if first_attrs.is_empty(); if iter .all(|arm| { - find_bool_lit(&arm.2.kind, is_if_let).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() + find_bool_lit(&arm.2.kind).map_or(false, |b| b == b0) && arm.3.is_none() && arm.0.is_empty() }); then { if let Some(last_pat) = last_pat_opt { @@ -144,7 +144,7 @@ where } /// Extract a `bool` or `{ bool }` -fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { +fn find_bool_lit(ex: &ExprKind<'_>) -> Option { match ex { ExprKind::Lit(Spanned { node: LitKind::Bool(b), .. @@ -156,7 +156,7 @@ fn find_bool_lit(ex: &ExprKind<'_>, is_if_let: bool) -> Option { .. }, _, - ) if is_if_let => { + ) => { if let ExprKind::Lit(Spanned { node: LitKind::Bool(b), .. }) = exp.kind diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 15513de7d860..582782f245fc 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -38,7 +38,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { normalized_pats[i + 1..] .iter() .enumerate() - .find_map(|(j, other)| pat.has_overlapping_values(other).then(|| i + 1 + j)) + .find_map(|(j, other)| pat.has_overlapping_values(other).then_some(i + 1 + j)) .unwrap_or(normalized_pats.len()) }) .collect(); @@ -55,7 +55,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { .zip(forwards_blocking_idxs[..i].iter().copied().rev()) .skip_while(|&(_, forward_block)| forward_block > i) .find_map(|((j, other), forward_block)| { - (forward_block == i || pat.has_overlapping_values(other)).then(|| j) + (forward_block == i || pat.has_overlapping_values(other)).then_some(j) }) .unwrap_or(0) }) @@ -365,7 +365,7 @@ impl<'a> NormalizedPat<'a> { (Self::Slice(pats, None), Self::Slice(front, Some(back))) | (Self::Slice(front, Some(back)), Self::Slice(pats, None)) => { // Here `pats` is an exact size match. If the combined lengths of `front` and `back` are greater - // then the minium length required will be greater than the length of `pats`. + // then the minimum length required will be greater than the length of `pats`. if pats.len() < front.len() + back.len() { return false; } diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 3077b999f4ee..d55082c66dc8 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1062,7 +1062,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar let start = scrutinee_span.hi(); let mut arm_spans = arms.iter().map(|arm| { let data = arm.span.data(); - (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi)) + (data.ctxt == SyntaxContext::root()).then_some((data.lo, data.hi)) }); let end = e.span.hi(); @@ -1096,7 +1096,7 @@ fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, ar parent: None, } .span(); - (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(()) + (!span_contains_cfg(cx, span)).then_some(next_start).ok_or(()) }); match found { Ok(start) => { diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 0704a5af5259..b0b15b3f54cd 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -89,6 +89,10 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>( source: MatchSource, ) -> Option<(Vec, &'static str)> { let mut helper = SigDropHelper::new(cx); + let scrutinee = match (source, &scrutinee.kind) { + (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, + _ => scrutinee, + }; helper.find_sig_drop(scrutinee).map(|drops| { let message = if source == MatchSource::Normal { "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 7dbfd95c50db..692e22a7c5cf 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -8,6 +8,7 @@ use rustc_hir as hir; use rustc_hir::def::Res; use rustc_hir::{Closure, Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; +use rustc_middle::ty::adjustment::Adjust; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; use std::borrow::Cow; @@ -49,35 +50,18 @@ fn is_option_filter_map<'tcx>(cx: &LateContext<'tcx>, filter_arg: &hir::Expr<'_> is_method(cx, map_arg, sym::unwrap) && is_method(cx, filter_arg, sym!(is_some)) } -/// lint use of `filter().map()` for `Iterators` -fn lint_filter_some_map_unwrap( +/// is `filter(|x| x.is_some()).map(|x| x.unwrap())` +fn is_filter_some_map_unwrap( cx: &LateContext<'_>, expr: &hir::Expr<'_>, filter_recv: &hir::Expr<'_>, filter_arg: &hir::Expr<'_>, map_arg: &hir::Expr<'_>, - target_span: Span, - methods_span: Span, -) { +) -> bool { let iterator = is_trait_method(cx, expr, sym::Iterator); let option = is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(filter_recv), sym::Option); - if (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) { - let msg = "`filter` for `Some` followed by `unwrap`"; - let help = "consider using `flatten` instead"; - let sugg = format!( - "{}", - reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, target_span),) - ); - span_lint_and_sugg( - cx, - OPTION_FILTER_MAP, - methods_span, - msg, - help, - sugg, - Applicability::MachineApplicable, - ); - } + + (iterator || option) && is_option_filter_map(cx, filter_arg, map_arg) } /// lint use of `filter().map()` or `find().map()` for `Iterators` @@ -93,15 +77,20 @@ pub(super) fn check<'tcx>( map_span: Span, is_find: bool, ) { - lint_filter_some_map_unwrap( - cx, - expr, - filter_recv, - filter_arg, - map_arg, - map_span, - filter_span.with_hi(expr.span.hi()), - ); + if is_filter_some_map_unwrap(cx, expr, filter_recv, filter_arg, map_arg) { + span_lint_and_sugg( + cx, + OPTION_FILTER_MAP, + filter_span.with_hi(expr.span.hi()), + "`filter` for `Some` followed by `unwrap`", + "consider using `flatten` instead", + reindent_multiline(Cow::Borrowed("flatten()"), true, indent_of(cx, map_span)).into_owned(), + Applicability::MachineApplicable, + ); + + return; + } + if_chain! { if is_trait_method(cx, map_recv, sym::Iterator); @@ -118,7 +107,7 @@ pub(super) fn check<'tcx>( // closure ends with is_some() or is_ok() if let PatKind::Binding(_, filter_param_id, _, None) = filter_pat.kind; if let ExprKind::MethodCall(path, [filter_arg], _) = filter_body.value.kind; - if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).ty_adt_def(); + if let Some(opt_ty) = cx.typeck_results().expr_ty(filter_arg).peel_refs().ty_adt_def(); if let Some(is_result) = if cx.tcx.is_diagnostic_item(sym::Option, opt_ty.did()) { Some(false) } else if cx.tcx.is_diagnostic_item(sym::Result, opt_ty.did()) { @@ -137,6 +126,19 @@ pub(super) fn check<'tcx>( if let ExprKind::MethodCall(seg, [map_arg, ..], _) = map_body.value.kind; if matches!(seg.ident.name, sym::expect | sym::unwrap | sym::unwrap_or); + // .filter(..).map(|y| f(y).copied().unwrap()) + // ~~~~ + let map_arg_peeled = match map_arg.kind { + ExprKind::MethodCall(method, [original_arg], _) if acceptable_methods(method) => { + original_arg + }, + _ => map_arg, + }; + + // .filter(|x| x.is_some()).map(|y| y[.acceptable_method()].unwrap()) + let simple_equal = path_to_local_id(filter_arg, filter_param_id) + && path_to_local_id(map_arg_peeled, map_param_id); + let eq_fallback = |a: &Expr<'_>, b: &Expr<'_>| { // in `filter(|x| ..)`, replace `*x` with `x` let a_path = if_chain! { @@ -145,25 +147,12 @@ pub(super) fn check<'tcx>( then { expr_path } else { a } }; // let the filter closure arg and the map closure arg be equal - if_chain! { - if path_to_local_id(a_path, filter_param_id); - if path_to_local_id(b, map_param_id); - if cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b); - then { - return true; - } - } - false - }; - - if match map_arg.kind { - ExprKind::MethodCall(method, [original_arg], _) => { - acceptable_methods(method) - && SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, original_arg) - }, - _ => SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg) + path_to_local_id(a_path, filter_param_id) + && path_to_local_id(b, map_param_id) + && cx.typeck_results().expr_ty_adjusted(a) == cx.typeck_results().expr_ty_adjusted(b) }; + if simple_equal || SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg_peeled); then { let span = filter_span.with_hi(expr.span.hi()); let (filter_name, lint) = if is_find { @@ -171,10 +160,22 @@ pub(super) fn check<'tcx>( } else { ("filter", MANUAL_FILTER_MAP) }; - let msg = format!("`{}(..).map(..)` can be simplified as `{0}_map(..)`", filter_name); - let to_opt = if is_result { ".ok()" } else { "" }; - let sugg = format!("{}_map(|{}| {}{})", filter_name, map_param_ident, - snippet(cx, map_arg.span, ".."), to_opt); + let msg = format!("`{filter_name}(..).map(..)` can be simplified as `{filter_name}_map(..)`"); + let (to_opt, deref) = if is_result { + (".ok()", String::new()) + } else { + let derefs = cx.typeck_results() + .expr_adjustments(map_arg) + .iter() + .filter(|adj| matches!(adj.kind, Adjust::Deref(_))) + .count(); + + ("", "*".repeat(derefs)) + }; + let sugg = format!( + "{filter_name}_map(|{map_param_ident}| {deref}{}{to_opt})", + snippet(cx, map_arg.span, ".."), + ); span_lint_and_sugg(cx, lint, span, &msg, "try", sugg, Applicability::MachineApplicable); } } diff --git a/clippy_lints/src/methods/manual_str_repeat.rs b/clippy_lints/src/methods/manual_str_repeat.rs index 68a75667914a..46d2fc493f81 100644 --- a/clippy_lints/src/methods/manual_str_repeat.rs +++ b/clippy_lints/src/methods/manual_str_repeat.rs @@ -43,7 +43,7 @@ fn parse_repeat_arg(cx: &LateContext<'_>, e: &Expr<'_>) -> Option { Some(RepeatKind::String) } else { let ty = ty.peel_refs(); - (ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then(|| RepeatKind::String) + (ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)).then_some(RepeatKind::String) } } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9bb7bb7a7aba..6981b4a66318 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -369,7 +369,7 @@ declare_clippy_lint! { /// let x: Result = Ok(10); /// x.expect_err("Testing expect_err"); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub ERR_EXPECT, style, r#"using `.err().expect("")` when `.expect_err("")` can be used"# @@ -2196,12 +2196,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Finds usages of [`char::is_digit`] - /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that - /// can be replaced with [`is_ascii_digit`] - /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or - /// [`is_ascii_hexdigit`] - /// (https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). + /// Finds usages of [`char::is_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_digit) that + /// can be replaced with [`is_ascii_digit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_digit) or + /// [`is_ascii_hexdigit`](https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_hexdigit). /// /// ### Why is this bad? /// `is_digit(..)` is slower and requires specifying the radix. @@ -2218,15 +2215,19 @@ declare_clippy_lint! { /// c.is_ascii_digit(); /// c.is_ascii_hexdigit(); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub IS_DIGIT_ASCII_RADIX, style, "use of `char::is_digit(..)` with literal radix of 10 or 16" } declare_clippy_lint! { + /// ### What it does + /// Checks for calling `take` function after `as_ref`. /// /// ### Why is this bad? + /// Redundant code. `take` writes `None` to its argument. + /// In this case the modification is useless as it's a temporary that cannot be read from afterwards. /// /// ### Example /// ```rust @@ -2238,7 +2239,7 @@ declare_clippy_lint! { /// let x = Some(3); /// x.as_ref(); /// ``` - #[clippy::version = "1.61.0"] + #[clippy::version = "1.62.0"] pub NEEDLESS_OPTION_TAKE, complexity, "using `.as_ref().take()` on a temporary value" @@ -2740,6 +2741,12 @@ impl Methods { } }, ("take", []) => needless_option_take::check(cx, expr, recv), + ("then", [arg]) => { + if !meets_msrv(self.msrv, msrvs::BOOL_THEN_SOME) { + return; + } + unnecessary_lazy_eval::check(cx, expr, recv, arg, "then_some"); + }, ("to_os_string" | "to_owned" | "to_path_buf" | "to_vec", []) => { implicit_clone::check(cx, name, expr, recv); }, diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index 3d1208824fa3..6af134019a47 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::eager_or_lazy::switch_to_lazy_eval; -use clippy_utils::source::{snippet, snippet_with_applicability, snippet_with_macro_callsite}; +use clippy_utils::source::{snippet, snippet_with_macro_callsite}; use clippy_utils::ty::{implements_trait, match_type}; use clippy_utils::{contains_return, is_trait_item, last_path_segment, paths}; use if_chain::if_chain; @@ -28,10 +28,10 @@ pub(super) fn check<'tcx>( cx: &LateContext<'_>, name: &str, fun: &hir::Expr<'_>, - self_expr: &hir::Expr<'_>, arg: &hir::Expr<'_>, or_has_args: bool, span: Span, + method_span: Span, ) -> bool { let is_default_default = || is_trait_item(cx, fun, sym::Default); @@ -52,24 +52,14 @@ pub(super) fn check<'tcx>( || (matches!(path, sym::new) && implements_default(arg, default_trait_id)); then { - let mut applicability = Applicability::MachineApplicable; - let hint = "unwrap_or_default()"; - let sugg_span = span; - - let sugg: String = format!( - "{}.{}", - snippet_with_applicability(cx, self_expr.span, "..", &mut applicability), - hint - ); - span_lint_and_sugg( cx, OR_FUN_CALL, - sugg_span, + method_span.with_hi(span.hi()), &format!("use of `{}` followed by a call to `{}`", name, path), "try this", - sugg, - applicability, + "unwrap_or_default()".to_string(), + Applicability::MachineApplicable, ); true @@ -171,7 +161,7 @@ pub(super) fn check<'tcx>( match inner_arg.kind { hir::ExprKind::Call(fun, or_args) => { let or_has_args = !or_args.is_empty(); - if !check_unwrap_or_default(cx, name, fun, self_arg, arg, or_has_args, expr.span) { + if !check_unwrap_or_default(cx, name, fun, arg, or_has_args, expr.span, method_span) { let fun_span = if or_has_args { None } else { Some(fun.span) }; check_general_case(cx, name, method_span, self_arg, arg, expr.span, fun_span); } diff --git a/clippy_lints/src/methods/unnecessary_lazy_eval.rs b/clippy_lints/src/methods/unnecessary_lazy_eval.rs index 21767d74c87b..1876c7fb9d05 100644 --- a/clippy_lints/src/methods/unnecessary_lazy_eval.rs +++ b/clippy_lints/src/methods/unnecessary_lazy_eval.rs @@ -20,8 +20,9 @@ pub(super) fn check<'tcx>( ) { 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(); - if is_option || is_result { + if is_option || is_result || is_bool { if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = arg.kind { let body = cx.tcx.hir().body(body); let body_expr = &body.value; @@ -33,8 +34,10 @@ pub(super) fn check<'tcx>( if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { let msg = if is_option { "unnecessary closure used to substitute value for `Option::None`" - } else { + } else if is_result { "unnecessary closure used to substitute value for `Result::Err`" + } else { + "unnecessary closure used with `bool::then`" }; let applicability = if body .params diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index be7df08d89f0..8224e80c9ccb 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -301,7 +301,7 @@ fn in_attributes_expansion(expr: &Expr<'_>) -> bool { use rustc_span::hygiene::MacroKind; if expr.span.from_expansion() { let data = expr.span.ctxt().outer_expn_data(); - matches!(data.kind, ExpnKind::Macro(MacroKind::Attr|MacroKind::Derive, _)) + matches!(data.kind, ExpnKind::Macro(MacroKind::Attr | MacroKind::Derive, _)) } else { false } diff --git a/clippy_lints/src/mismatching_type_param_order.rs b/clippy_lints/src/mismatching_type_param_order.rs index d466d54a6ba5..254d9a70010a 100644 --- a/clippy_lints/src/mismatching_type_param_order.rs +++ b/clippy_lints/src/mismatching_type_param_order.rs @@ -9,7 +9,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint}; declare_clippy_lint! { /// ### What it does /// Checks for type parameters which are positioned inconsistently between - /// a type definition and impl block. Specifically, a paramater in an impl + /// a type definition and impl block. Specifically, a parameter in an impl /// block which has the same name as a parameter in the type def, but is in /// a different place. /// diff --git a/clippy_lints/src/new_without_default.rs b/clippy_lints/src/new_without_default.rs index 093ec389335d..5c45ee6d94ad 100644 --- a/clippy_lints/src/new_without_default.rs +++ b/clippy_lints/src/new_without_default.rs @@ -88,15 +88,9 @@ impl<'tcx> LateLintPass<'tcx> for NewWithoutDefault { // shouldn't be implemented when it is hidden in docs return; } - if impl_item - .generics - .params - .iter() - .any(|gen| matches!(gen.kind, hir::GenericParamKind::Type { .. })) - { - // when the result of `new()` depends on a type parameter we should not require - // an - // impl of `Default` + if !impl_item.generics.params.is_empty() { + // when the result of `new()` depends on a parameter we should not require + // an impl of `Default` return; } if_chain! { diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index 3ab4b6c4f6fa..6ad6837f0e35 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// and friends since the string is already preprocessed when Clippy lints /// can see it. /// - /// # Example + /// ### Example /// ```rust /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape /// let two = "\033\0"; // \033 intended as null-3-3 diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index d461668077e0..413a740be25a 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -11,8 +11,8 @@ use rustc_hir::def_id::DefId; use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData}; use rustc_hir::intravisit::{walk_expr, walk_stmt, FnKind, Visitor}; use rustc_hir::{ - Arm, Closure, Block, Body, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, PathSegment, - QPath, Stmt, StmtKind, TyKind, UnOp, + Arm, Block, Body, Closure, Expr, ExprKind, Guard, HirId, ImplicitSelfKind, Let, Local, Pat, PatKind, Path, + PathSegment, QPath, Stmt, StmtKind, TyKind, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index ea5a8f0858b6..44f153cffac5 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -146,7 +146,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> }); if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(local_id), .. })) = e.kind { match some_captures.get(local_id) - .or_else(|| (method_sugg == "map_or_else").then(|| ()).and_then(|_| none_captures.get(local_id))) + .or_else(|| (method_sugg == "map_or_else").then_some(()).and_then(|_| none_captures.get(local_id))) { Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return None, Some(CaptureKind::Ref(Mutability::Not)) if as_mut => return None, diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 8571607054a0..8534d8a29f10 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -502,7 +502,7 @@ fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Optio .iter() .filter_map(get_rptr_lm) .filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span)) + .map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span)) .collect(); if let Some(args) = args && !args.is_empty() diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 899568a933f7..f0155ed6051f 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -1,16 +1,19 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::higher; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{eq_expr_value, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt}; +use clippy_utils::{ + eq_expr_value, get_parent_node, is_else_clause, is_lang_ctor, path_to_local, path_to_local_id, peel_blocks, + peel_blocks_with_stmt, +}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultOk}; -use rustc_hir::{BindingAnnotation, Expr, ExprKind, PatKind}; +use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::Ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; +use rustc_span::{sym, symbol::Symbol}; declare_clippy_lint! { /// ### What it does @@ -39,135 +42,190 @@ declare_clippy_lint! { declare_lint_pass!(QuestionMark => [QUESTION_MARK]); -impl QuestionMark { - /// Checks if the given expression on the given context matches the following structure: +enum IfBlockType<'hir> { + /// An `if x.is_xxx() { a } else { b } ` expression. /// - /// ```ignore - /// if option.is_none() { - /// return None; - /// } - /// ``` + /// Contains: caller (x), caller_type, call_sym (is_xxx), if_then (a), if_else (b) + IfIs( + &'hir Expr<'hir>, + Ty<'hir>, + Symbol, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), + /// An `if let Xxx(a) = b { c } else { d }` expression. /// - /// ```ignore - /// if result.is_err() { - /// return result; - /// } - /// ``` - /// - /// If it matches, it will suggest to use the question mark operator instead - fn check_is_none_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); - if let ExprKind::MethodCall(segment, args, _) = &cond.kind; - if let Some(subject) = args.get(0); - if (Self::option_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_none)) || - (Self::result_check_and_early_return(cx, subject, then) && segment.ident.name == sym!(is_err)); - then { - let mut applicability = Applicability::MachineApplicable; - let receiver_str = &Sugg::hir_with_applicability(cx, subject, "..", &mut applicability); - let mut replacement: Option = None; - if let Some(else_inner) = r#else { - if eq_expr_value(cx, subject, peel_blocks(else_inner)) { - replacement = Some(format!("Some({}?)", receiver_str)); - } - } else if Self::moves_by_default(cx, subject) - && !matches!(subject.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)) - { - replacement = Some(format!("{}.as_ref()?;", receiver_str)); + /// Contains: let_pat_qpath (Xxx), let_pat_type, let_pat_sym (a), let_expr (b), if_then (c), + /// if_else (d) + IfLet( + &'hir QPath<'hir>, + Ty<'hir>, + Symbol, + &'hir Expr<'hir>, + &'hir Expr<'hir>, + Option<&'hir Expr<'hir>>, + ), +} + +/// Checks if the given expression on the given context matches the following structure: +/// +/// ```ignore +/// if option.is_none() { +/// return None; +/// } +/// ``` +/// +/// ```ignore +/// if result.is_err() { +/// return result; +/// } +/// ``` +/// +/// If it matches, it will suggest to use the question mark operator instead +fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if_chain! { + if let Some(higher::If { cond, then, r#else }) = higher::If::hir(expr); + if !is_else_clause(cx.tcx, expr); + if let ExprKind::MethodCall(segment, args, _) = &cond.kind; + if let Some(caller) = args.get(0); + let caller_ty = cx.typeck_results().expr_ty(caller); + let if_block = IfBlockType::IfIs(caller, caller_ty, segment.ident.name, then, r#else); + if is_early_return(sym::Option, cx, &if_block) || is_early_return(sym::Result, cx, &if_block); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, caller.span, "..", &mut applicability); + let by_ref = !caller_ty.is_copy_modulo_regions(cx.tcx.at(caller.span), cx.param_env) && + !matches!(caller.kind, ExprKind::Call(..) | ExprKind::MethodCall(..)); + let sugg = if let Some(else_inner) = r#else { + if eq_expr_value(cx, caller, peel_blocks(else_inner)) { + format!("Some({}?)", receiver_str) } else { - replacement = Some(format!("{}?;", receiver_str)); + return; } + } else { + format!("{}{}?;", receiver_str, if by_ref { ".as_ref()" } else { "" }) + }; - if let Some(replacement_str) = replacement { - span_lint_and_sugg( - cx, - QUESTION_MARK, - expr.span, - "this block may be rewritten with the `?` operator", - "replace it with", - replacement_str, - applicability, - ); - } - } + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + sugg, + applicability, + ); } } +} - fn check_if_let_some_or_err_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>) { - if_chain! { - if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else: Some(if_else) }) - = higher::IfLet::hir(cx, expr); - if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; - if (Self::option_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, OptionSome)) || - (Self::result_check_and_early_return(cx, let_expr, if_else) && is_lang_ctor(cx, path1, ResultOk)); - - if let PatKind::Binding(annot, bind_id, _, _) = fields[0].kind; +fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if_chain! { + if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr); + if !is_else_clause(cx.tcx, expr); + if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; + if let PatKind::Binding(annot, bind_id, ident, _) = fields[0].kind; + let caller_ty = cx.typeck_results().expr_ty(let_expr); + let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else); + if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id)) + || is_early_return(sym::Result, cx, &if_block); + if if_else.map(|e| eq_expr_value(cx, let_expr, peel_blocks(e))).filter(|e| *e).is_none(); + then { + let mut applicability = Applicability::MachineApplicable; + let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut); - if path_to_local_id(peel_blocks(if_then), bind_id); - then { - let mut applicability = Applicability::MachineApplicable; - let receiver_str = snippet_with_applicability(cx, let_expr.span, "..", &mut applicability); - let replacement = format!("{}{}?", receiver_str, if by_ref { ".as_ref()" } else { "" },); + let requires_semi = matches!(get_parent_node(cx.tcx, expr.hir_id), Some(Node::Stmt(_))); + let sugg = format!( + "{}{}?{}", + receiver_str, + if by_ref { ".as_ref()" } else { "" }, + if requires_semi { ";" } else { "" } + ); + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this block may be rewritten with the `?` operator", + "replace it with", + sugg, + applicability, + ); + } + } +} - span_lint_and_sugg( - cx, - QUESTION_MARK, - expr.span, - "this if-let-else may be rewritten with the `?` operator", - "replace it with", - replacement, - applicability, - ); +fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_>) -> bool { + match *if_block { + IfBlockType::IfIs(caller, caller_ty, call_sym, if_then, _) => { + // If the block could be identified as `if x.is_none()/is_err()`, + // we then only need to check the if_then return to see if it is none/err. + is_type_diagnostic_item(cx, caller_ty, smbl) + && expr_return_none_or_err(smbl, cx, if_then, caller, None) + && match smbl { + sym::Option => call_sym == sym!(is_none), + sym::Result => call_sym == sym!(is_err), + _ => false, + } + }, + IfBlockType::IfLet(qpath, let_expr_ty, let_pat_sym, let_expr, if_then, if_else) => { + is_type_diagnostic_item(cx, let_expr_ty, smbl) + && match smbl { + sym::Option => { + // We only need to check `if let Some(x) = option` not `if let None = option`, + // because the later one will be suggested as `if option.is_none()` thus causing conflict. + is_lang_ctor(cx, qpath, OptionSome) + && if_else.is_some() + && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, None) + }, + sym::Result => { + (is_lang_ctor(cx, qpath, ResultOk) + && if_else.is_some() + && expr_return_none_or_err(smbl, cx, if_else.unwrap(), let_expr, Some(let_pat_sym))) + || is_lang_ctor(cx, qpath, ResultErr) + && expr_return_none_or_err(smbl, cx, if_then, let_expr, Some(let_pat_sym)) + }, + _ => false, + } + }, + } +} + +fn expr_return_none_or_err( + smbl: Symbol, + cx: &LateContext<'_>, + expr: &Expr<'_>, + cond_expr: &Expr<'_>, + err_sym: Option, +) -> bool { + match peel_blocks_with_stmt(expr).kind { + ExprKind::Ret(Some(ret_expr)) => expr_return_none_or_err(smbl, cx, ret_expr, cond_expr, err_sym), + ExprKind::Path(ref qpath) => match smbl { + sym::Option => is_lang_ctor(cx, qpath, OptionNone), + sym::Result => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), + _ => false, + }, + ExprKind::Call(call_expr, args_expr) => { + if_chain! { + if smbl == sym::Result; + if let ExprKind::Path(QPath::Resolved(_, path)) = &call_expr.kind; + if let Some(segment) = path.segments.first(); + if let Some(err_sym) = err_sym; + if let Some(arg) = args_expr.first(); + if let ExprKind::Path(QPath::Resolved(_, arg_path)) = &arg.kind; + if let Some(PathSegment { ident, .. }) = arg_path.segments.first(); + then { + return segment.ident.name == sym::Err && err_sym == ident.name; + } } - } - } - - fn result_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool { - Self::is_result(cx, expr) && Self::expression_returns_unmodified_err(nested_expr, expr) - } - - fn option_check_and_early_return(cx: &LateContext<'_>, expr: &Expr<'_>, nested_expr: &Expr<'_>) -> bool { - Self::is_option(cx, expr) && Self::expression_returns_none(cx, nested_expr) - } - - fn moves_by_default(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - !expr_ty.is_copy_modulo_regions(cx.tcx.at(expression.span), cx.param_env) - } - - fn is_option(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - is_type_diagnostic_item(cx, expr_ty, sym::Option) - } - - fn is_result(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - let expr_ty = cx.typeck_results().expr_ty(expression); - - is_type_diagnostic_item(cx, expr_ty, sym::Result) - } - - fn expression_returns_none(cx: &LateContext<'_>, expression: &Expr<'_>) -> bool { - match peel_blocks_with_stmt(expression).kind { - ExprKind::Ret(Some(expr)) => Self::expression_returns_none(cx, expr), - ExprKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone), - _ => false, - } - } - - fn expression_returns_unmodified_err(expr: &Expr<'_>, cond_expr: &Expr<'_>) -> bool { - match peel_blocks_with_stmt(expr).kind { - ExprKind::Ret(Some(ret_expr)) => Self::expression_returns_unmodified_err(ret_expr, cond_expr), - ExprKind::Path(_) => path_to_local(expr).is_some() && path_to_local(expr) == path_to_local(cond_expr), - _ => false, - } + false + }, + _ => false, } } impl<'tcx> LateLintPass<'tcx> for QuestionMark { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - Self::check_is_none_or_err_and_early_return(cx, expr); - Self::check_if_let_some_or_err_and_early_return(cx, expr); + check_is_none_or_err_and_early_return(cx, expr); + check_if_let_some_or_err_and_early_return(cx, expr); } } diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index 67129299e2f9..f9a9b0691935 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -19,7 +19,7 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// Regex::new("|") + /// Regex::new("(") /// ``` #[clippy::version = "pre 1.29.0"] pub INVALID_REGEX, diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index bf318c055dad..5dcdab5b8ab9 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -99,7 +99,7 @@ declare_clippy_lint! { #[derive(Default)] pub(crate) struct Shadow { - bindings: Vec>>, + bindings: Vec<(FxHashMap>, LocalDefId)>, } impl_lint_pass!(Shadow => [SHADOW_SAME, SHADOW_REUSE, SHADOW_UNRELATED]); @@ -121,7 +121,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { let HirId { owner, local_id } = id; // get (or insert) the list of items for this owner and symbol - let data = self.bindings.last_mut().unwrap(); + let (ref mut data, scope_owner) = *self.bindings.last_mut().unwrap(); let items_with_name = data.entry(ident.name).or_default(); // check other bindings with the same name, most recently seen first @@ -131,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { return; } - if is_shadow(cx, owner, prev, local_id) { + if is_shadow(cx, scope_owner, prev, local_id) { let prev_hir_id = HirId { owner, local_id: prev }; lint_shadow(cx, pat, prev_hir_id, ident.span); // only lint against the "nearest" shadowed binding @@ -144,11 +144,9 @@ impl<'tcx> LateLintPass<'tcx> for Shadow { fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) { let hir = cx.tcx.hir(); - if !matches!( - hir.body_owner_kind(hir.body_owner_def_id(body.id())), - BodyOwnerKind::Closure - ) { - self.bindings.push(FxHashMap::default()); + let owner_id = hir.body_owner_def_id(body.id()); + if !matches!(hir.body_owner_kind(owner_id), BodyOwnerKind::Closure) { + self.bindings.push((FxHashMap::default(), owner_id)); } } diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs new file mode 100644 index 000000000000..56f2a7bae152 --- /dev/null +++ b/clippy_lints/src/std_instead_of_core.rs @@ -0,0 +1,134 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir::{def::Res, HirId, Path, PathSegment}; +use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::{sym, symbol::kw, Symbol}; + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds items imported through `std` when available through `core`. + /// + /// ### Why is this bad? + /// + /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure + /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates + /// migrating to become `no_std` compatible. + /// + /// ### Example + /// ```rust + /// use std::hash::Hasher; + /// ``` + /// Use instead: + /// ```rust + /// use core::hash::Hasher; + /// ``` + #[clippy::version = "1.64.0"] + pub STD_INSTEAD_OF_CORE, + restriction, + "type is imported from std when available in core" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds items imported through `std` when available through `alloc`. + /// + /// ### Why is this bad? + /// + /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from + /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful + /// for crates migrating to become `no_std` compatible. + /// + /// ### Example + /// ```rust + /// use std::vec::Vec; + /// ``` + /// Use instead: + /// ```rust + /// # extern crate alloc; + /// use alloc::vec::Vec; + /// ``` + #[clippy::version = "1.64.0"] + pub STD_INSTEAD_OF_ALLOC, + restriction, + "type is imported from std when available in alloc" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Finds items imported through `alloc` when available through `core`. + /// + /// ### Why is this bad? + /// + /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are + /// imported from alloc to ensure disabling `alloc` does not cause the crate to fail to compile. This lint + /// is also useful for crates migrating to become `no_std` compatible. + /// + /// ### Example + /// ```rust + /// # extern crate alloc; + /// use alloc::slice::from_ref; + /// ``` + /// Use instead: + /// ```rust + /// use core::slice::from_ref; + /// ``` + #[clippy::version = "1.64.0"] + pub ALLOC_INSTEAD_OF_CORE, + restriction, + "type is imported from alloc when available in core" +} + +declare_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); + +impl<'tcx> LateLintPass<'tcx> for StdReexports { + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { + // std_instead_of_core + check_path(cx, path, sym::std, sym::core, STD_INSTEAD_OF_CORE); + // std_instead_of_alloc + check_path(cx, path, sym::std, sym::alloc, STD_INSTEAD_OF_ALLOC); + // alloc_instead_of_core + check_path(cx, path, sym::alloc, sym::core, ALLOC_INSTEAD_OF_CORE); + } +} + +fn check_path(cx: &LateContext<'_>, path: &Path<'_>, krate: Symbol, suggested_crate: Symbol, lint: &'static Lint) { + if_chain! { + // check if path resolves to the suggested crate. + if let Res::Def(_, def_id) = path.res; + if suggested_crate == cx.tcx.crate_name(def_id.krate); + + // check if the first segment of the path is the crate we want to identify + if let Some(path_root_segment) = get_first_segment(path); + + // check if the path matches the crate we want to suggest the other path for. + if krate == path_root_segment.ident.name; + then { + span_lint_and_help( + cx, + lint, + path.span, + &format!("used import from `{}` instead of `{}`", krate, suggested_crate), + None, + &format!("consider importing the item from `{}`", suggested_crate), + ); + } + } +} + +/// Returns the first named segment of a [`Path`]. +/// +/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`] +/// is returned. +fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { + let segment = path.segments.first()?; + + // A global path will have PathRoot as the first segment. In this case, return the segment after. + if segment.ident.name == kw::PathRoot { + path.segments.get(1) + } else { + Some(segment) + } +} diff --git a/clippy_lints/src/swap_ptr_to_ref.rs b/clippy_lints/src/swap_ptr_to_ref.rs index 75d3b040c968..3cbbda80f3a9 100644 --- a/clippy_lints/src/swap_ptr_to_ref.rs +++ b/clippy_lints/src/swap_ptr_to_ref.rs @@ -73,7 +73,7 @@ fn is_ptr_to_ref(cx: &LateContext<'_>, e: &Expr<'_>, ctxt: SyntaxContext) -> (bo && let ExprKind::Unary(UnOp::Deref, derefed_expr) = borrowed_expr.kind && cx.typeck_results().expr_ty(derefed_expr).is_unsafe_ptr() { - (true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then(|| derefed_expr.span)) + (true, (borrowed_expr.span.ctxt() == ctxt || derefed_expr.span.ctxt() == ctxt).then_some(derefed_expr.span)) } else { (false, None) } diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index ac63d1823371..0a42a31fb8cf 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -1,20 +1,20 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::source::{snippet, snippet_with_applicability}; +use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; +use clippy_utils::source::{snippet, snippet_opt, snippet_with_applicability}; use clippy_utils::{SpanlessEq, SpanlessHash}; use core::hash::{Hash, Hasher}; use if_chain::if_chain; +use itertools::Itertools; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, TraitItem, Ty, TyKind, - WherePredicate, + GenericArg, GenericBound, Generics, Item, ItemKind, Node, Path, PathSegment, PredicateOrigin, QPath, + TraitBoundModifier, TraitItem, TraitRef, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::Span; -use std::fmt::Write as _; +use rustc_span::{BytePos, Span}; declare_clippy_lint! { /// ### What it does @@ -35,8 +35,8 @@ declare_clippy_lint! { /// ``` #[clippy::version = "1.38.0"] pub TYPE_REPETITION_IN_BOUNDS, - pedantic, - "Types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" + nursery, + "types are repeated unnecessary in trait bounds use `+` instead of using `T: _, T: _`" } declare_clippy_lint! { @@ -63,10 +63,26 @@ declare_clippy_lint! { /// /// fn func(arg: T) where T: Clone + Default {} /// ``` + /// + /// ```rust + /// fn foo(bar: T) {} + /// ``` + /// Use instead: + /// ```rust + /// fn foo(bar: T) {} + /// ``` + /// + /// ```rust + /// fn foo(bar: T) where T: Default + Default {} + /// ``` + /// Use instead: + /// ```rust + /// fn foo(bar: T) where T: Default {} + /// ``` #[clippy::version = "1.47.0"] pub TRAIT_DUPLICATION_IN_BOUNDS, - pedantic, - "Check if the same trait bounds are specified twice during a function declaration" + nursery, + "check if the same trait bounds are specified more than once during a generic declaration" } #[derive(Copy, Clone)] @@ -87,6 +103,19 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { fn check_generics(&mut self, cx: &LateContext<'tcx>, gen: &'tcx Generics<'_>) { self.check_type_repetition(cx, gen); check_trait_bound_duplication(cx, gen); + check_bounds_or_where_duplication(cx, gen); + } + + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + // special handling for self trait bounds as these are not considered generics + // ie. trait Foo: Display {} + if let Item { + kind: ItemKind::Trait(_, _, _, bounds, ..), + .. + } = item + { + rollup_traits(cx, bounds, "these bounds contain repeated elements"); + } } fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'tcx>) { @@ -178,30 +207,18 @@ impl TraitBounds { ); then { - let mut hint_string = format!( - "consider combining the bounds: `{}:", - snippet(cx, p.bounded_ty.span, "_") + let trait_bounds = v + .iter() + .copied() + .chain(p.bounds.iter()) + .filter_map(get_trait_info_from_bound) + .map(|(_, _, span)| snippet_with_applicability(cx, span, "..", &mut applicability)) + .join(" + "); + let hint_string = format!( + "consider combining the bounds: `{}: {}`", + snippet(cx, p.bounded_ty.span, "_"), + trait_bounds, ); - for b in v.iter() { - if let GenericBound::Trait(ref poly_trait_ref, _) = b { - let path = &poly_trait_ref.trait_ref.path; - let _ = write!(hint_string, - " {} +", - snippet_with_applicability(cx, path.span, "..", &mut applicability) - ); - } - } - for b in p.bounds.iter() { - if let GenericBound::Trait(ref poly_trait_ref, _) = b { - let path = &poly_trait_ref.trait_ref.path; - let _ = write!(hint_string, - " {} +", - snippet_with_applicability(cx, path.span, "..", &mut applicability) - ); - } - } - hint_string.truncate(hint_string.len() - 2); - hint_string.push('`'); span_lint_and_help( cx, TYPE_REPETITION_IN_BOUNDS, @@ -253,10 +270,107 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { } } +#[derive(PartialEq, Eq, Hash, Debug)] +struct ComparableTraitRef(Res, Vec); + +fn check_bounds_or_where_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) { + if gen.span.from_expansion() { + return; + } + + for predicate in gen.predicates { + if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate { + let msg = if predicate.in_where_clause() { + "these where clauses contain repeated elements" + } else { + "these bounds contain repeated elements" + }; + rollup_traits(cx, bound_predicate.bounds, msg); + } + } +} + fn get_trait_info_from_bound<'a>(bound: &'a GenericBound<'_>) -> Option<(Res, &'a [PathSegment<'a>], Span)> { - if let GenericBound::Trait(t, _) = bound { - Some((t.trait_ref.path.res, t.trait_ref.path.segments, t.span)) + if let GenericBound::Trait(t, tbm) = bound { + let trait_path = t.trait_ref.path; + let trait_span = { + let path_span = trait_path.span; + if let TraitBoundModifier::Maybe = tbm { + path_span.with_lo(path_span.lo() - BytePos(1)) // include the `?` + } else { + path_span + } + }; + Some((trait_path.res, trait_path.segments, trait_span)) } else { None } } + +// FIXME: ComparableTraitRef does not support nested bounds needed for associated_type_bounds +fn into_comparable_trait_ref(trait_ref: &TraitRef<'_>) -> ComparableTraitRef { + ComparableTraitRef( + trait_ref.path.res, + trait_ref + .path + .segments + .iter() + .filter_map(|segment| { + // get trait bound type arguments + Some(segment.args?.args.iter().filter_map(|arg| { + if_chain! { + if let GenericArg::Type(ty) = arg; + if let TyKind::Path(QPath::Resolved(_, path)) = ty.kind; + then { return Some(path.res) } + } + None + })) + }) + .flatten() + .collect(), + ) +} + +fn rollup_traits(cx: &LateContext<'_>, bounds: &[GenericBound<'_>], msg: &str) { + let mut map = FxHashMap::default(); + let mut repeated_res = false; + + let only_comparable_trait_refs = |bound: &GenericBound<'_>| { + if let GenericBound::Trait(t, _) = bound { + Some((into_comparable_trait_ref(&t.trait_ref), t.span)) + } else { + None + } + }; + + for bound in bounds.iter().filter_map(only_comparable_trait_refs) { + let (comparable_bound, span_direct) = bound; + if map.insert(comparable_bound, span_direct).is_some() { + repeated_res = true; + } + } + + if_chain! { + if repeated_res; + if let [first_trait, .., last_trait] = bounds; + then { + let all_trait_span = first_trait.span().to(last_trait.span()); + + let mut traits = map.values() + .filter_map(|span| snippet_opt(cx, *span)) + .collect::>(); + traits.sort_unstable(); + let traits = traits.join(" + "); + + span_lint_and_sugg( + cx, + TRAIT_DUPLICATION_IN_BOUNDS, + all_trait_span, + msg, + "try", + traits, + Applicability::MachineApplicable + ); + } + } +} diff --git a/clippy_lints/src/types/box_collection.rs b/clippy_lints/src/types/box_collection.rs index 21a9558ec076..ba51404d2148 100644 --- a/clippy_lints/src/types/box_collection.rs +++ b/clippy_lints/src/types/box_collection.rs @@ -15,19 +15,17 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ sym::String => "", _ => "<..>", }; + + let box_content = format!("{outer}{generic}", outer = item_type); span_lint_and_help( cx, BOX_COLLECTION, hir_ty.span, &format!( - "you seem to be trying to use `Box<{outer}{generic}>`. Consider using just `{outer}{generic}`", - outer=item_type, - generic = generic), + "you seem to be trying to use `Box<{box_content}>`. Consider using just `{box_content}`"), None, &format!( - "`{outer}{generic}` is already on the heap, `Box<{outer}{generic}>` makes an extra allocation", - outer=item_type, - generic = generic) + "`{box_content}` is already on the heap, `Box<{box_content}>` makes an extra allocation") ); true } else { @@ -39,7 +37,18 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_ fn get_std_collection(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option { let param = qpath_generic_tys(qpath).next()?; let id = path_def_id(cx, param)?; - cx.tcx - .get_diagnostic_name(id) - .filter(|&name| matches!(name, sym::HashMap | sym::String | sym::Vec)) + cx.tcx.get_diagnostic_name(id).filter(|&name| { + matches!( + name, + sym::HashMap + | sym::String + | sym::Vec + | sym::HashSet + | sym::VecDeque + | sym::LinkedList + | sym::BTreeMap + | sym::BTreeSet + | sym::BinaryHeap + ) + }) } diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 025dd57e83aa..04f16fd2161c 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -265,14 +265,28 @@ fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span } } +fn get_body_search_span(cx: &LateContext<'_>) -> Option { + let body = cx.enclosing_body?; + let map = cx.tcx.hir(); + let mut span = map.body(body).value.span; + for (_, node) in map.parent_iter(body.hir_id) { + match node { + Node::Expr(e) => span = e.span, + Node::Block(_) | Node::Arm(_) | Node::Stmt(_) | Node::Local(_) => (), + _ => break, + } + } + Some(span) +} + fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool { let source_map = cx.sess().source_map(); let ctxt = span.ctxt(); if ctxt == SyntaxContext::root() - && let Some(body) = cx.enclosing_body + && let Some(search_span) = get_body_search_span(cx) { if let Ok(unsafe_line) = source_map.lookup_line(span.lo()) - && let Some(body_span) = walk_span_to_context(cx.tcx.hir().body(body).value.span, SyntaxContext::root()) + && let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root()) && let Ok(body_line) = source_map.lookup_line(body_span.lo()) && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf) && let Some(src) = unsafe_line.sf.src.as_deref() diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index cf509455aad0..aec028d5c482 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -1,43 +1,40 @@ use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::get_parent_node; use clippy_utils::source::snippet_with_macro_callsite; -use clippy_utils::visitors::for_each_value_source; +use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Expr, ExprKind, PatKind, Stmt, StmtKind}; +use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::{self, Ty, TypeVisitable, TypeSuperVisitable, TypeVisitor}; +use rustc_middle::ty; use super::LET_UNIT_VALUE; -pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { - if let StmtKind::Local(local) = stmt.kind - && let Some(init) = local.init +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) { + if let Some(init) = local.init && !local.pat.span.from_expansion() - && !in_external_macro(cx.sess(), stmt.span) + && !in_external_macro(cx.sess(), local.span) && cx.typeck_results().pat_ty(local.pat).is_unit() { - let needs_inferred = for_each_value_source(init, &mut |e| if needs_inferred_result_ty(cx, e) { - ControlFlow::Continue(()) - } else { - ControlFlow::Break(()) - }).is_continue(); - - if needs_inferred { - if !matches!(local.pat.kind, PatKind::Wild) { + if (local.ty.map_or(false, |ty| !matches!(ty.kind, TyKind::Infer)) + || matches!(local.pat.kind, PatKind::Tuple([], None))) + && expr_needs_inferred_result(cx, init) + { + if !matches!(local.pat.kind, PatKind::Wild | PatKind::Tuple([], None)) { span_lint_and_then( cx, LET_UNIT_VALUE, - stmt.span, + local.span, "this let-binding has unit value", |diag| { - diag.span_suggestion( - local.pat.span, - "use a wild (`_`) binding", - "_", - Applicability::MaybeIncorrect, // snippet - ); + diag.span_suggestion( + local.pat.span, + "use a wild (`_`) binding", + "_", + Applicability::MaybeIncorrect, // snippet + ); }, ); } @@ -45,15 +42,15 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { span_lint_and_then( cx, LET_UNIT_VALUE, - stmt.span, + local.span, "this let-binding has unit value", |diag| { if let Some(expr) = &local.init { let snip = snippet_with_macro_callsite(cx, expr.span, "()"); diag.span_suggestion( - stmt.span, + local.span, "omit the `let` binding", - format!("{};", snip), + format!("{snip};"), Applicability::MachineApplicable, // snippet ); } @@ -63,48 +60,106 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) { } } -fn needs_inferred_result_ty(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { - let id = match e.kind { +/// Checks sub-expressions which create the value returned by the given expression for whether +/// return value inference is needed. This checks through locals to see if they also need inference +/// at this point. +/// +/// e.g. +/// ```rust,ignore +/// let bar = foo(); +/// let x: u32 = if true { baz() } else { bar }; +/// ``` +/// Here the sources of the value assigned to `x` would be `baz()`, and `foo()` via the +/// initialization of `bar`. If both `foo` and `baz` have a return type which require type +/// inference then this function would return `true`. +fn expr_needs_inferred_result<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { + // The locals used for initialization which have yet to be checked. + let mut locals_to_check = Vec::new(); + // All the locals which have been added to `locals_to_check`. Needed to prevent cycles. + let mut seen_locals = HirIdSet::default(); + if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { + return false; + } + while let Some(id) = locals_to_check.pop() { + if let Some(Node::Local(l)) = get_parent_node(cx.tcx, id) { + if !l.ty.map_or(true, |ty| matches!(ty.kind, TyKind::Infer)) { + return false; + } + if let Some(e) = l.init { + if !each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { + return false; + } + } else if for_each_local_assignment(cx, id, |e| { + if each_value_source_needs_inference(cx, e, &mut locals_to_check, &mut seen_locals) { + ControlFlow::Continue(()) + } else { + ControlFlow::Break(()) + } + }) + .is_break() + { + return false; + } + } + } + + true +} + +fn each_value_source_needs_inference( + cx: &LateContext<'_>, + e: &Expr<'_>, + locals_to_check: &mut Vec, + seen_locals: &mut HirIdSet, +) -> bool { + for_each_value_source(e, &mut |e| { + if needs_inferred_result_ty(cx, e, locals_to_check, seen_locals) { + ControlFlow::Continue(()) + } else { + ControlFlow::Break(()) + } + }) + .is_continue() +} + +fn needs_inferred_result_ty( + cx: &LateContext<'_>, + e: &Expr<'_>, + locals_to_check: &mut Vec, + seen_locals: &mut HirIdSet, +) -> bool { + let (id, args) = match e.kind { ExprKind::Call( Expr { kind: ExprKind::Path(ref path), hir_id, .. }, - _, + args, ) => match cx.qpath_res(path, *hir_id) { - Res::Def(DefKind::AssocFn | DefKind::Fn, id) => id, + Res::Def(DefKind::AssocFn | DefKind::Fn, id) => (id, args), _ => return false, }, - ExprKind::MethodCall(..) => match cx.typeck_results().type_dependent_def_id(e.hir_id) { - Some(id) => id, + ExprKind::MethodCall(_, args, _) => match cx.typeck_results().type_dependent_def_id(e.hir_id) { + Some(id) => (id, args), None => return false, }, + ExprKind::Path(QPath::Resolved(None, path)) => { + if let Res::Local(id) = path.res + && seen_locals.insert(id) + { + locals_to_check.push(id); + } + return true; + }, _ => return false, }; let sig = cx.tcx.fn_sig(id).skip_binder(); if let ty::Param(output_ty) = *sig.output().kind() { - sig.inputs().iter().all(|&ty| !ty_contains_param(ty, output_ty.index)) + sig.inputs().iter().zip(args).all(|(&ty, arg)| { + !ty.is_param(output_ty.index) || each_value_source_needs_inference(cx, arg, locals_to_check, seen_locals) + }) } else { false } } - -fn ty_contains_param(ty: Ty<'_>, index: u32) -> bool { - struct Visitor(u32); - impl<'tcx> TypeVisitor<'tcx> for Visitor { - type BreakTy = (); - fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow { - if let ty::Param(ty) = *ty.kind() { - if ty.index == self.0 { - ControlFlow::BREAK - } else { - ControlFlow::CONTINUE - } - } else { - ty.super_visit_with(self) - } - } - } - ty.visit_with(&mut Visitor(index)).is_break() -} diff --git a/clippy_lints/src/unit_types/mod.rs b/clippy_lints/src/unit_types/mod.rs index a9e2073dec25..6aa86a57c9bd 100644 --- a/clippy_lints/src/unit_types/mod.rs +++ b/clippy_lints/src/unit_types/mod.rs @@ -3,7 +3,7 @@ mod unit_arg; mod unit_cmp; mod utils; -use rustc_hir::{Expr, Stmt}; +use rustc_hir::{Expr, Local}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -98,9 +98,9 @@ declare_clippy_lint! { declare_lint_pass!(UnitTypes => [LET_UNIT_VALUE, UNIT_CMP, UNIT_ARG]); -impl LateLintPass<'_> for UnitTypes { - fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) { - let_unit_value::check(cx, stmt); +impl<'tcx> LateLintPass<'tcx> for UnitTypes { + fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) { + let_unit_value::check(cx, local); } fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index a94f0357977e..b309653291b1 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -20,8 +20,8 @@ use rustc_hir::def_id::DefId; use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::intravisit::Visitor; use rustc_hir::{ - BinOpKind, Block, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty, TyKind, - UnOp, + BinOpKind, Block, Closure, Expr, ExprKind, HirId, Item, Local, MutTy, Mutability, Node, Path, Stmt, StmtKind, Ty, + TyKind, UnOp, }; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; @@ -730,8 +730,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, and_then_args) = expr.kind; if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]); if and_then_args.len() == 5; - if let ExprKind::Closure { body, .. } = &and_then_args[4].kind; - let body = cx.tcx.hir().body(*body); + if let ExprKind::Closure(&Closure { body, .. }) = &and_then_args[4].kind; + let body = cx.tcx.hir().body(body); let only_expr = peel_blocks_with_stmt(&body.value); if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind; if let ExprKind::Path(..) = span_call_args[0].kind; diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 6518e0a6ea07..92934c16d4b4 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -17,7 +17,7 @@ use if_chain::if_chain; use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{ - self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath, + self as hir, def::DefKind, intravisit, intravisit::Visitor, Closure, ExprKind, Item, ItemKind, Mutability, QPath, }; use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId}; use rustc_middle::hir::nested_filter; @@ -843,7 +843,7 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option { fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> { DEFAULT_LINT_LEVELS .iter() - .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level)) + .find_map(|(group_name, group_level)| (*group_name == lint_group).then_some(*group_level)) } pub(super) fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { @@ -958,7 +958,7 @@ fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hi } fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool { - if let ExprKind::Closure { body, .. } = closure_expr.kind { + if let ExprKind::Closure(&Closure { body, .. }) = closure_expr.kind { let mut scanner = IsMultiSpanScanner::new(cx); intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body)); return scanner.is_multi_part(); @@ -1018,7 +1018,7 @@ impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> { /// This visitor finds the highest applicability value in the visited expressions struct ApplicabilityResolver<'a, 'hir> { cx: &'a LateContext<'hir>, - /// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES` + /// This is the index of highest `Applicability` for `paths::APPLICABILITY_VALUES` applicability_index: Option, } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 67b2bc8c3f3c..08b889475201 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -515,7 +515,7 @@ impl Write { args.push(arg, span); } - parser.errors.is_empty().then(move || args) + parser.errors.is_empty().then_some(args) } /// Checks the arguments of `print[ln]!` and `write[ln]!` calls. It will return a tuple of two diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 431b09d53c33..b226026323be 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -169,7 +169,12 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (Field(lp, lf), Field(rp, rf)) => eq_id(*lf, *rf) && eq_expr(lp, rp), (Match(ls, la), Match(rs, ra)) => eq_expr(ls, rs) && over(la, ra, eq_arm), (Closure(lb, lc, la, lm, lf, le, _), Closure(rb, rc, ra, rm, rf, re, _)) => { - eq_closure_binder(lb, rb) && lc == rc && la.is_async() == ra.is_async() && lm == rm && eq_fn_decl(lf, rf) && eq_expr(le, re) + eq_closure_binder(lb, rb) + && lc == rc + && la.is_async() == ra.is_async() + && lm == rm + && eq_fn_decl(lf, rf) + && eq_expr(le, re) }, (Async(lc, _, lb), Async(rc, _, rb)) => lc == rc && eq_block(lb, rb), (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), @@ -564,8 +569,9 @@ pub fn eq_fn_decl(l: &FnDecl, r: &FnDecl) -> bool { pub fn eq_closure_binder(l: &ClosureBinder, r: &ClosureBinder) -> bool { match (l, r) { (ClosureBinder::NotPresent, ClosureBinder::NotPresent) => true, - (ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) => - lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r)), + (ClosureBinder::For { generic_params: lp, .. }, ClosureBinder::For { generic_params: rp, .. }) => { + lp.len() == rp.len() && std::iter::zip(lp.iter(), rp.iter()).all(|(l, r)| eq_generic_param(l, r)) + }, _ => false, } } @@ -610,7 +616,7 @@ pub fn eq_ext(l: &Extern, r: &Extern) -> bool { use Extern::*; match (l, r) { (None, None) | (Implicit(_), Implicit(_)) => true, - (Explicit(l,_), Explicit(r,_)) => eq_str_lit(l, r), + (Explicit(l, _), Explicit(r, _)) => eq_str_lit(l, r), _ => false, } } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 1a8e8c996316..77c974582ecb 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -6,9 +6,9 @@ use rustc_data_structures::fx::FxHasher; use rustc_hir::def::Res; use rustc_hir::HirIdMap; use rustc_hir::{ - ArrayLen, BinOpKind, Closure, Block, BodyId, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, HirId, - InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath, Stmt, - StmtKind, Ty, TyKind, TypeBinding, + ArrayLen, BinOpKind, Block, BodyId, Closure, Expr, ExprField, ExprKind, FnRetTy, GenericArg, GenericArgs, Guard, + HirId, InlineAsmOperand, Let, Lifetime, LifetimeName, ParamName, Pat, PatField, PatKind, Path, PathSegment, QPath, + Stmt, StmtKind, Ty, TyKind, TypeBinding, }; use rustc_lexer::{tokenize, TokenKind}; use rustc_lint::LateContext; @@ -102,7 +102,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> { impl HirEqInterExpr<'_, '_, '_> { pub fn eq_stmt(&mut self, left: &Stmt<'_>, right: &Stmt<'_>) -> bool { match (&left.kind, &right.kind) { - (&StmtKind::Local(l, ), &StmtKind::Local(r, )) => { + (&StmtKind::Local(l), &StmtKind::Local(r)) => { // This additional check ensures that the type of the locals are equivalent even if the init // expression or type have some inferred parts. if let Some((typeck_lhs, typeck_rhs)) = self.inner.maybe_typeck_results { @@ -922,7 +922,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { std::mem::discriminant(&b.kind).hash(&mut self.s); match &b.kind { - StmtKind::Local(local, ) => { + StmtKind::Local(local) => { self.hash_pat(local.pat); if let Some(init) = local.init { self.hash_expr(init); diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0089e1b64006..2fdda9fac162 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -93,7 +93,9 @@ use rustc_middle::ty::fast_reject::SimplifiedTypeGen::{ ArraySimplifiedType, BoolSimplifiedType, CharSimplifiedType, FloatSimplifiedType, IntSimplifiedType, PtrSimplifiedType, SliceSimplifiedType, StrSimplifiedType, UintSimplifiedType, }; -use rustc_middle::ty::{layout::IntegerExt, BorrowKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture}; +use rustc_middle::ty::{ + layout::IntegerExt, BorrowKind, ClosureKind, DefIdTree, Ty, TyCtxt, TypeAndMut, TypeVisitable, UpvarCapture, +}; use rustc_middle::ty::{FloatTy, IntTy, UintTy}; use rustc_semver::RustcVersion; use rustc_session::Session; @@ -105,7 +107,7 @@ use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::Integer; use crate::consts::{constant, Constant}; -use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type}; +use crate::ty::{can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type, ty_is_fn_once_param}; use crate::visitors::expr_visitor_no_bodies; pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { @@ -890,7 +892,7 @@ pub fn capture_local_usage<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Captur Node::Expr(e) => match e.kind { ExprKind::AddrOf(_, mutability, _) => return CaptureKind::Ref(mutability), ExprKind::Index(..) | ExprKind::Unary(UnOp::Deref, _) => capture = CaptureKind::Ref(Mutability::Not), - ExprKind::Assign(lhs, ..) | ExprKind::Assign(_, lhs, _) if lhs.hir_id == child_id => { + ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) if lhs.hir_id == child_id => { return CaptureKind::Ref(Mutability::Mut); }, ExprKind::Field(..) => { @@ -1016,7 +1018,7 @@ pub fn can_move_expr_to_closure<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<' captures: HirIdMap::default(), }; v.visit_expr(expr); - v.allow_closure.then(|| v.captures) + v.allow_closure.then_some(v.captures) } /// Returns the method names and argument list of nested method call expressions that make up @@ -1197,16 +1199,54 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio } /// Gets the loop or closure enclosing the given expression, if any. -pub fn get_enclosing_loop_or_closure<'tcx>(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - for (_, node) in tcx.hir().parent_iter(expr.hir_id) { +pub fn get_enclosing_loop_or_multi_call_closure<'tcx>( + cx: &LateContext<'tcx>, + expr: &Expr<'_>, +) -> Option<&'tcx Expr<'tcx>> { + for (_, node) in cx.tcx.hir().parent_iter(expr.hir_id) { match node { - Node::Expr( - e @ Expr { - kind: ExprKind::Loop(..) | ExprKind::Closure { .. }, - .. + Node::Expr(e) => match e.kind { + ExprKind::Closure { .. } => { + if let rustc_ty::Closure(_, subs) = cx.typeck_results().expr_ty(e).kind() + && subs.as_closure().kind() == ClosureKind::FnOnce + { + continue; + } + let is_once = walk_to_expr_usage(cx, e, |node, id| { + let Node::Expr(e) = node else { + return None; + }; + match e.kind { + ExprKind::Call(f, _) if f.hir_id == id => Some(()), + ExprKind::Call(f, args) => { + let i = args.iter().position(|arg| arg.hir_id == id)?; + let sig = expr_sig(cx, f)?; + let predicates = sig + .predicates_id() + .map_or(cx.param_env, |id| cx.tcx.param_env(id)) + .caller_bounds(); + sig.input(i).and_then(|ty| { + ty_is_fn_once_param(cx.tcx, ty.skip_binder(), predicates).then_some(()) + }) + }, + ExprKind::MethodCall(_, args, _) => { + let i = args.iter().position(|arg| arg.hir_id == id)?; + let id = cx.typeck_results().type_dependent_def_id(e.hir_id)?; + let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i]; + ty_is_fn_once_param(cx.tcx, ty, cx.tcx.param_env(id).caller_bounds()).then_some(()) + }, + _ => None, + } + }) + .is_some(); + if !is_once { + return Some(e); + } }, - ) => return Some(e), - Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), + ExprKind::Loop(..) => return Some(e), + _ => (), + }, + Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), _ => break, } } diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index b09c929f76e2..9e238c6f1ac0 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -12,6 +12,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { + 1,62,0 { BOOL_THEN_SOME } 1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN } 1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST } 1,51,0 { BORROW_AS_PTR, UNSIGNED_ABS } diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 6542e77113b4..05429d05d9eb 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -163,6 +163,7 @@ pub const STR_BYTES: [&str; 4] = ["core", "str", "", "bytes"]; pub const STR_CHARS: [&str; 4] = ["core", "str", "", "chars"]; pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "", "ends_with"]; pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"]; +pub const STR_FROM_UTF8_UNCHECKED: [&str; 4] = ["core", "str", "converts", "from_utf8_unchecked"]; pub const STR_LEN: [&str; 4] = ["core", "str", "", "len"]; pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "", "starts_with"]; #[cfg(feature = "internal")] diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index f88a92fb11c1..1197fe914de4 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -353,7 +353,7 @@ pub fn snippet_with_context<'a>( /// span containing `m!(0)`. pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option { let outer_span = hygiene::walk_chain(span, outer); - (outer_span.ctxt() == outer).then(|| outer_span) + (outer_span.ctxt() == outer).then_some(outer_span) } /// Removes block comments from the given `Vec` of lines. diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 4326a103d44b..bad291dfc251 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -2,6 +2,7 @@ #![deny(clippy::missing_docs_in_private_items)] use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite}; +use crate::ty::expr_sig; use crate::{get_parent_expr_for_hir, higher}; use rustc_ast::util::parser::AssocOp; use rustc_ast::{ast, token}; @@ -18,7 +19,6 @@ use rustc_span::source_map::{BytePos, CharPos, Pos, Span, SyntaxContext}; use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use std::borrow::Cow; use std::fmt::{Display, Write as _}; -use std::iter; use std::ops::{Add, Neg, Not, Sub}; /// A helper type to build suggestion correctly handling parentheses. @@ -861,23 +861,37 @@ impl<'tcx> DerefDelegate<'_, 'tcx> { /// indicates whether the function from `parent_expr` takes its args by double reference fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool { - let (call_args, inputs) = match parent_expr.kind { + let ty = match parent_expr.kind { ExprKind::MethodCall(_, call_args, _) => { - if let Some(method_did) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) { - (call_args, self.cx.tcx.fn_sig(method_did).skip_binder().inputs()) + if let Some(sig) = self + .cx + .typeck_results() + .type_dependent_def_id(parent_expr.hir_id) + .map(|did| self.cx.tcx.fn_sig(did).skip_binder()) + { + call_args + .iter() + .position(|arg| arg.hir_id == cmt_hir_id) + .map(|i| sig.inputs()[i]) } else { return false; } }, ExprKind::Call(func, call_args) => { - let typ = self.cx.typeck_results().expr_ty(func); - (call_args, typ.fn_sig(self.cx.tcx).skip_binder().inputs()) + if let Some(sig) = expr_sig(self.cx, func) { + call_args + .iter() + .position(|arg| arg.hir_id == cmt_hir_id) + .and_then(|i| sig.input(i)) + .map(ty::Binder::skip_binder) + } else { + return false; + } }, _ => return false, }; - iter::zip(call_args, inputs) - .any(|(arg, ty)| arg.hir_id == cmt_hir_id && matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref())) + ty.map_or(false, |ty| matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref())) } } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index a426fa1b0ffc..a05d633d980c 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -147,7 +147,7 @@ pub fn has_iter_method(cx: &LateContext<'_>, probably_ref_ty: Ty<'_>) -> Option< /// * [`get_trait_def_id`](super::get_trait_def_id) to get a trait [`DefId`]. /// * [Common tools for writing lints] for an example how to use this function and other options. /// -/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait +/// [Common tools for writing lints]: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/common_tools_writing_lints.md#checking-if-a-type-implements-a-specific-trait pub fn implements_trait<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, @@ -501,7 +501,7 @@ pub fn all_predicates_of(tcx: TyCtxt<'_>, id: DefId) -> impl Iterator { - Sig(Binder<'tcx, FnSig<'tcx>>), + Sig(Binder<'tcx, FnSig<'tcx>>, Option), Closure(Option<&'tcx FnDecl<'tcx>>, Binder<'tcx, FnSig<'tcx>>), Trait(Binder<'tcx, Ty<'tcx>>, Option>>), } @@ -510,7 +510,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// bounds only for variadic functions, otherwise this will panic. pub fn input(self, i: usize) -> Option>> { match self { - Self::Sig(sig) => { + Self::Sig(sig, _) => { if sig.c_variadic() { sig.inputs().map_bound(|inputs| inputs.get(i).copied()).transpose() } else { @@ -527,7 +527,7 @@ impl<'tcx> ExprFnSig<'tcx> { /// functions, otherwise this will panic. pub fn input_with_hir(self, i: usize) -> Option<(Option<&'tcx hir::Ty<'tcx>>, Binder<'tcx, Ty<'tcx>>)> { match self { - Self::Sig(sig) => { + Self::Sig(sig, _) => { if sig.c_variadic() { sig.inputs() .map_bound(|inputs| inputs.get(i).copied()) @@ -549,22 +549,29 @@ impl<'tcx> ExprFnSig<'tcx> { /// specified. pub fn output(self) -> Option>> { match self { - Self::Sig(sig) | Self::Closure(_, sig) => Some(sig.output()), + Self::Sig(sig, _) | Self::Closure(_, sig) => Some(sig.output()), Self::Trait(_, output) => output, } } + + pub fn predicates_id(&self) -> Option { + if let ExprFnSig::Sig(_, id) = *self { id } else { None } + } } /// If the expression is function like, get the signature for it. pub fn expr_sig<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { if let Res::Def(DefKind::Fn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::AssocFn, id) = path_res(cx, expr) { - Some(ExprFnSig::Sig(cx.tcx.fn_sig(id))) + Some(ExprFnSig::Sig(cx.tcx.fn_sig(id), Some(id))) } else { ty_sig(cx, cx.typeck_results().expr_ty_adjusted(expr).peel_refs()) } } fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> { + if ty.is_box() { + return ty_sig(cx, ty.boxed_ty()); + } match *ty.kind() { ty::Closure(id, subs) => { let decl = id @@ -572,8 +579,9 @@ fn ty_sig<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option> .and_then(|id| cx.tcx.hir().fn_decl_by_hir_id(cx.tcx.hir().local_def_id_to_hir_id(id))); Some(ExprFnSig::Closure(decl, subs.as_closure().sig())) }, - ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs))), - ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig)), + ty::FnDef(id, subs) => Some(ExprFnSig::Sig(cx.tcx.bound_fn_sig(id).subst(cx.tcx, subs), Some(id))), + ty::Opaque(id, _) => ty_sig(cx, cx.tcx.type_of(id)), + ty::FnPtr(sig) => Some(ExprFnSig::Sig(sig, None)), ty::Dynamic(bounds, _) => { let lang_items = cx.tcx.lang_items(); match bounds.principal() { @@ -789,3 +797,33 @@ pub fn variant_of_res<'tcx>(cx: &LateContext<'tcx>, res: Res) -> Option<&'tcx Va _ => None, } } + +/// Checks if the type is a type parameter implementing `FnOnce`, but not `FnMut`. +pub fn ty_is_fn_once_param<'tcx>(tcx: TyCtxt<'_>, ty: Ty<'tcx>, predicates: &'tcx [Predicate<'_>]) -> bool { + let ty::Param(ty) = *ty.kind() else { + return false; + }; + let lang = tcx.lang_items(); + let (Some(fn_once_id), Some(fn_mut_id), Some(fn_id)) + = (lang.fn_once_trait(), lang.fn_mut_trait(), lang.fn_trait()) + else { + return false; + }; + predicates + .iter() + .try_fold(false, |found, p| { + if let PredicateKind::Trait(p) = p.kind().skip_binder() + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + && ty.index == self_ty.index + { + // This should use `super_traits_of`, but that's a private function. + if p.trait_ref.def_id == fn_once_id { + return Some(true); + } else if p.trait_ref.def_id == fn_mut_id || p.trait_ref.def_id == fn_id { + return None; + } + } + Some(found) + }) + .unwrap_or(false) +} diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 68cfa8c1aa8e..bae8ad9f5659 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -5,7 +5,7 @@ use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::intravisit::{self, walk_block, walk_expr, Visitor}; use rustc_hir::{ - Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, QPath, Stmt, UnOp, + Arm, Block, BlockCheckMode, Body, BodyId, Expr, ExprKind, HirId, ItemId, ItemKind, Let, Pat, QPath, Stmt, UnOp, UnsafeSource, Unsafety, }; use rustc_lint::LateContext; @@ -13,6 +13,74 @@ use rustc_middle::hir::map::Map; use rustc_middle::hir::nested_filter; use rustc_middle::ty::adjustment::Adjust; use rustc_middle::ty::{self, Ty, TypeckResults}; +use rustc_span::Span; + +mod internal { + /// Trait for visitor functions to control whether or not to descend to child nodes. Implemented + /// for only two types. `()` always descends. `Descend` allows controlled descent. + pub trait Continue { + fn descend(&self) -> bool; + } +} +use internal::Continue; + +impl Continue for () { + fn descend(&self) -> bool { + true + } +} + +/// Allows for controlled descent when using visitor functions. Use `()` instead when always +/// descending into child nodes. +#[derive(Clone, Copy)] +pub enum Descend { + Yes, + No, +} +impl From for Descend { + fn from(from: bool) -> Self { + if from { Self::Yes } else { Self::No } + } +} +impl Continue for Descend { + fn descend(&self) -> bool { + matches!(self, Self::Yes) + } +} + +/// Calls the given function once for each expression contained. This does not enter any bodies or +/// nested items. +pub fn for_each_expr<'tcx, B, C: Continue>( + node: impl Visitable<'tcx>, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow, +) -> Option { + struct V { + f: F, + res: Option, + } + impl<'tcx, B, C: Continue, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow> Visitor<'tcx> for V { + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + if self.res.is_some() { + return; + } + match (self.f)(e) { + ControlFlow::Continue(c) if c.descend() => walk_expr(self, e), + ControlFlow::Break(b) => self.res = Some(b), + ControlFlow::Continue(_) => (), + } + } + + // Avoid unnecessary `walk_*` calls. + fn visit_ty(&mut self, _: &'tcx hir::Ty<'tcx>) {} + fn visit_pat(&mut self, _: &'tcx Pat<'tcx>) {} + fn visit_qpath(&mut self, _: &'tcx QPath<'tcx>, _: HirId, _: Span) {} + // Avoid monomorphising all `visit_*` functions. + fn visit_nested_item(&mut self, _: ItemId) {} + } + let mut v = V { f, res: None }; + node.visit(&mut v); + v.res +} /// Convenience method for creating a `Visitor` with just `visit_expr` overridden and nested /// bodies (i.e. closures) are visited. @@ -617,3 +685,49 @@ pub fn any_temporaries_need_ordered_drop<'tcx>(cx: &LateContext<'tcx>, e: &'tcx }) .is_break() } + +/// Runs the given function for each path expression referencing the given local which occur after +/// the given expression. +pub fn for_each_local_assignment<'tcx, B>( + cx: &LateContext<'tcx>, + local_id: HirId, + f: impl FnMut(&'tcx Expr<'tcx>) -> ControlFlow, +) -> ControlFlow { + struct V<'cx, 'tcx, F, B> { + cx: &'cx LateContext<'tcx>, + local_id: HirId, + res: ControlFlow, + f: F, + } + impl<'cx, 'tcx, F: FnMut(&'tcx Expr<'tcx>) -> ControlFlow, B> Visitor<'tcx> for V<'cx, 'tcx, F, B> { + type NestedFilter = nested_filter::OnlyBodies; + fn nested_visit_map(&mut self) -> Self::Map { + self.cx.tcx.hir() + } + + fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { + if let ExprKind::Assign(lhs, rhs, _) = e.kind + && self.res.is_continue() + && path_to_local_id(lhs, self.local_id) + { + self.res = (self.f)(rhs); + self.visit_expr(rhs); + } else { + walk_expr(self, e); + } + } + } + + if let Some(b) = get_enclosing_block(cx, local_id) { + let mut v = V { + cx, + local_id, + res: ControlFlow::Continue(()), + f, + }; + v.visit_block(b); + v.res + } else { + ControlFlow::Continue(()) + } +} diff --git a/lintcheck/Cargo.toml b/lintcheck/Cargo.toml index 504d58b5197a..737c845c0451 100644 --- a/lintcheck/Cargo.toml +++ b/lintcheck/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lintcheck" version = "0.0.1" -description = "tool to monitor impact of changes in Clippys lints on a part of the ecosystem" +description = "tool to monitor impact of changes in Clippy's lints on a part of the ecosystem" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/rust-clippy" @@ -11,7 +11,7 @@ publish = false [dependencies] cargo_metadata = "0.14" -clap = "3.1" +clap = "3.2" flate2 = "1.0" rayon = "1.5.1" serde = { version = "1.0", features = ["derive"] } diff --git a/lintcheck/README.md b/lintcheck/README.md index 8c169506e533..6f3d23382ce1 100644 --- a/lintcheck/README.md +++ b/lintcheck/README.md @@ -70,7 +70,7 @@ is explicitly specified in the options. ### Fix mode You can run `./lintcheck/target/debug/lintcheck --fix` which will run Clippy with `--fix` and -print a warning if Clippys suggestions fail to apply (if the resulting code does not build). +print a warning if Clippy's suggestions fail to apply (if the resulting code does not build). This lets us spot bad suggestions or false positives automatically in some cases. Please note that the target dir should be cleaned afterwards since clippy will modify diff --git a/lintcheck/src/main.rs b/lintcheck/src/main.rs index dff9d27db0a6..9ee25280f046 100644 --- a/lintcheck/src/main.rs +++ b/lintcheck/src/main.rs @@ -545,7 +545,7 @@ fn lintcheck_needs_rerun(lintcheck_logs_path: &Path) -> bool { fn main() { // assert that we launch lintcheck from the repo root (via cargo lintcheck) if std::fs::metadata("lintcheck/Cargo.toml").is_err() { - eprintln!("lintcheck needs to be run from clippys repo root!\nUse `cargo lintcheck` alternatively."); + eprintln!("lintcheck needs to be run from clippy's repo root!\nUse `cargo lintcheck` alternatively."); std::process::exit(3); } @@ -586,7 +586,7 @@ fn main() { .map(|o| String::from_utf8_lossy(&o.stdout).into_owned()) .expect("could not get clippy version!"); - // download and extract the crates, then run clippy on them and collect clippys warnings + // download and extract the crates, then run clippy on them and collect clippy's warnings // flatten into one big list of warnings let crates = read_crates(&config.sources_toml_path); diff --git a/rust-toolchain b/rust-toolchain index 6cc6d5036b37..e693e6837592 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-30" +channel = "nightly-2022-07-15" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/compile-test.rs b/tests/compile-test.rs index bf7a39edf4c9..3615d07154df 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -394,6 +394,7 @@ const RUSTFIX_COVERAGE_KNOWN_EXCEPTIONS: &[&str] = &[ "single_component_path_imports_nested_first.rs", "string_add.rs", "toplevel_ref_arg_non_rustfix.rs", + "trait_duplication_in_bounds.rs", "unit_arg.rs", "unnecessary_clone.rs", "unnecessary_lazy_eval_unfixable.rs", diff --git a/tests/dogfood.rs b/tests/dogfood.rs index fffc53603424..5697e8680cd6 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -74,10 +74,16 @@ fn run_clippy_for_package(project: &str, args: &[&str]) { .env("CARGO_INCREMENTAL", "0") .arg("clippy") .arg("--all-targets") - .arg("--all-features") - .arg("--") - .args(args) - .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir + .arg("--all-features"); + + if let Ok(dogfood_args) = std::env::var("__CLIPPY_DOGFOOD_ARGS") { + for arg in dogfood_args.split_whitespace() { + command.arg(arg); + } + } + + command.arg("--").args(args); + command.arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir if cfg!(feature = "internal") { // internal lints only exist if we build with the internal feature diff --git a/tests/lint_message_convention.rs b/tests/lint_message_convention.rs index dd1d44120360..c3aae1a9aa2d 100644 --- a/tests/lint_message_convention.rs +++ b/tests/lint_message_convention.rs @@ -1,8 +1,10 @@ +#![feature(once_cell)] #![cfg_attr(feature = "deny-warnings", deny(warnings))] #![warn(rust_2018_idioms, unused_lifetimes)] use std::ffi::OsStr; use std::path::PathBuf; +use std::sync::LazyLock; use regex::RegexSet; @@ -14,42 +16,45 @@ struct Message { impl Message { fn new(path: PathBuf) -> Self { - let content: String = std::fs::read_to_string(&path).unwrap(); // we don't want the first letter after "error: ", "help: " ... to be capitalized // also no punctuation (except for "?" ?) at the end of a line - let regex_set: RegexSet = RegexSet::new(&[ - r"error: [A-Z]", - r"help: [A-Z]", - r"warning: [A-Z]", - r"note: [A-Z]", - r"try this: [A-Z]", - r"error: .*[.!]$", - r"help: .*[.!]$", - r"warning: .*[.!]$", - r"note: .*[.!]$", - r"try this: .*[.!]$", - ]) - .unwrap(); + static REGEX_SET: LazyLock = LazyLock::new(|| { + RegexSet::new(&[ + r"error: [A-Z]", + r"help: [A-Z]", + r"warning: [A-Z]", + r"note: [A-Z]", + r"try this: [A-Z]", + r"error: .*[.!]$", + r"help: .*[.!]$", + r"warning: .*[.!]$", + r"note: .*[.!]$", + r"try this: .*[.!]$", + ]) + .unwrap() + }); // sometimes the first character is capitalized and it is legal (like in "C-like enum variants") or // we want to ask a question ending in "?" - let exceptions_set: RegexSet = RegexSet::new(&[ - r".*C-like enum variant discriminant is not portable to 32-bit targets", - r".*did you mean `unix`?", - r".*the arguments may be inverted...", - r".*Intel x86 assembly syntax used", - r".*AT&T x86 assembly syntax used", - r".*remove .*the return type...", - r"note: Clippy version: .*", - r"the compiler unexpectedly panicked. this is a bug.", - ]) - .unwrap(); + static EXCEPTIONS_SET: LazyLock = LazyLock::new(|| { + RegexSet::new(&[ + r"\.\.\.$", + r".*C-like enum variant discriminant is not portable to 32-bit targets", + r".*Intel x86 assembly syntax used", + r".*AT&T x86 assembly syntax used", + r"note: Clippy version: .*", + r"the compiler unexpectedly panicked. this is a bug.", + ]) + .unwrap() + }); + + let content: String = std::fs::read_to_string(&path).unwrap(); let bad_lines = content .lines() - .filter(|line| regex_set.matches(line).matched_any()) + .filter(|line| REGEX_SET.matches(line).matched_any()) // ignore exceptions - .filter(|line| !exceptions_set.matches(line).matched_any()) + .filter(|line| !EXCEPTIONS_SET.matches(line).matched_any()) .map(ToOwned::to_owned) .collect::>(); diff --git a/tests/ui-cargo/duplicate_mod/fail/src/d.rs b/tests/ui-cargo/duplicate_mod/fail/src/d.rs index e69de29bb2d1..8b137891791f 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/d.rs +++ b/tests/ui-cargo/duplicate_mod/fail/src/d.rs @@ -0,0 +1 @@ + diff --git a/tests/ui-cargo/duplicate_mod/fail/src/main.rs b/tests/ui-cargo/duplicate_mod/fail/src/main.rs index 99ca538b6e4a..6478e65ac81a 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/main.rs +++ b/tests/ui-cargo/duplicate_mod/fail/src/main.rs @@ -1,4 +1,4 @@ -#[feature(lint_reasons)] +#![feature(lint_reasons)] mod a; @@ -25,5 +25,4 @@ mod d3; #[allow(clippy::duplicate_mod)] mod d4; - fn main() {} diff --git a/tests/ui-cargo/duplicate_mod/fail/src/main.stderr b/tests/ui-cargo/duplicate_mod/fail/src/main.stderr index 61df1ad5d501..b450a2b18f25 100644 --- a/tests/ui-cargo/duplicate_mod/fail/src/main.stderr +++ b/tests/ui-cargo/duplicate_mod/fail/src/main.stderr @@ -24,6 +24,17 @@ LL | | mod c3; | = help: replace all but one `mod` item with `use` items +error: file is loaded as a module multiple times: `$DIR/d.rs` + --> $DIR/main.rs:18:1 + | +LL | mod d; + | ^^^^^^ first loaded here +LL | / #[path = "d.rs"] +LL | | mod d2; + | |_______^ loaded again here + | + = help: replace all but one `mod` item with `use` items + error: file is loaded as a module multiple times: `$DIR/from_other_module.rs` --> $DIR/main.rs:15:1 | @@ -38,16 +49,5 @@ LL | | mod m; | = help: replace all but one `mod` item with `use` items -error: file is loaded as a module multiple times: `$DIR/b.rs` - --> $DIR/main.rs:18:1 - | -LL | mod d; - | ^^^^^^ first loaded here -LL | / #[path = "d.rs"] -LL | | mod d2; - | |_______^ loaded again here - | - = help: replace all but one `mod` item with `use` items - error: aborting due to 4 previous errors diff --git a/tests/ui/auxiliary/macro_rules.rs b/tests/ui/auxiliary/macro_rules.rs index 5bd2c2799f03..83a0af6b87ac 100644 --- a/tests/ui/auxiliary/macro_rules.rs +++ b/tests/ui/auxiliary/macro_rules.rs @@ -135,3 +135,8 @@ macro_rules! manual_rem_euclid { let _: i32 = ((value % 4) + 4) % 4; }; } + +#[macro_export] +macro_rules! equatable_if_let { + ($a:ident) => {{ if let 2 = $a {} }}; +} diff --git a/tests/ui/box_collection.rs b/tests/ui/box_collection.rs index e00f061f28a9..1a74cdb3ff65 100644 --- a/tests/ui/box_collection.rs +++ b/tests/ui/box_collection.rs @@ -6,7 +6,7 @@ unused )] -use std::collections::HashMap; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}; macro_rules! boxit { ($init:expr, $x:ty) => { @@ -18,7 +18,7 @@ fn test_macro() { boxit!(Vec::new(), Vec); } -fn test(foo: Box>) {} +fn test1(foo: Box>) {} fn test2(foo: Box)>) { // pass if #31 is fixed @@ -29,6 +29,18 @@ fn test3(foo: Box) {} fn test4(foo: Box>) {} +fn test5(foo: Box>) {} + +fn test6(foo: Box>) {} + +fn test7(foo: Box>) {} + +fn test8(foo: Box>) {} + +fn test9(foo: Box>) {} + +fn test10(foo: Box>) {} + fn test_local_not_linted() { let _: Box>; } diff --git a/tests/ui/box_collection.stderr b/tests/ui/box_collection.stderr index 6de85d05a99f..2b28598ded92 100644 --- a/tests/ui/box_collection.stderr +++ b/tests/ui/box_collection.stderr @@ -1,8 +1,8 @@ error: you seem to be trying to use `Box>`. Consider using just `Vec<..>` - --> $DIR/box_collection.rs:21:14 + --> $DIR/box_collection.rs:21:15 | -LL | fn test(foo: Box>) {} - | ^^^^^^^^^^^^^^ +LL | fn test1(foo: Box>) {} + | ^^^^^^^^^^^^^^ | = note: `-D clippy::box-collection` implied by `-D warnings` = help: `Vec<..>` is already on the heap, `Box>` makes an extra allocation @@ -23,5 +23,53 @@ LL | fn test4(foo: Box>) {} | = help: `HashMap<..>` is already on the heap, `Box>` makes an extra allocation -error: aborting due to 3 previous errors +error: you seem to be trying to use `Box>`. Consider using just `HashSet<..>` + --> $DIR/box_collection.rs:32:15 + | +LL | fn test5(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^ + | + = help: `HashSet<..>` is already on the heap, `Box>` makes an extra allocation + +error: you seem to be trying to use `Box>`. Consider using just `VecDeque<..>` + --> $DIR/box_collection.rs:34:15 + | +LL | fn test6(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^^ + | + = help: `VecDeque<..>` is already on the heap, `Box>` makes an extra allocation + +error: you seem to be trying to use `Box>`. Consider using just `LinkedList<..>` + --> $DIR/box_collection.rs:36:15 + | +LL | fn test7(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: `LinkedList<..>` is already on the heap, `Box>` makes an extra allocation + +error: you seem to be trying to use `Box>`. Consider using just `BTreeMap<..>` + --> $DIR/box_collection.rs:38:15 + | +LL | fn test8(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: `BTreeMap<..>` is already on the heap, `Box>` makes an extra allocation + +error: you seem to be trying to use `Box>`. Consider using just `BTreeSet<..>` + --> $DIR/box_collection.rs:40:15 + | +LL | fn test9(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^^ + | + = help: `BTreeSet<..>` is already on the heap, `Box>` makes an extra allocation + +error: you seem to be trying to use `Box>`. Consider using just `BinaryHeap<..>` + --> $DIR/box_collection.rs:42:16 + | +LL | fn test10(foo: Box>) {} + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: `BinaryHeap<..>` is already on the heap, `Box>` makes an extra allocation + +error: aborting due to 9 previous errors diff --git a/tests/ui/branches_sharing_code/false_positives.rs b/tests/ui/branches_sharing_code/false_positives.rs index 064482009517..5e3a1a29693f 100644 --- a/tests/ui/branches_sharing_code/false_positives.rs +++ b/tests/ui/branches_sharing_code/false_positives.rs @@ -1,6 +1,8 @@ #![allow(dead_code)] #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] +use std::sync::Mutex; + // ################################## // # Issue clippy#7369 // ################################## @@ -38,4 +40,56 @@ fn main() { let (y, x) = x; foo(x, y) }; + + let m = Mutex::new(0u32); + let l = m.lock().unwrap(); + let _ = if true { + drop(l); + println!("foo"); + m.lock().unwrap(); + 0 + } else if *l == 0 { + drop(l); + println!("foo"); + println!("bar"); + m.lock().unwrap(); + 1 + } else { + drop(l); + println!("foo"); + println!("baz"); + m.lock().unwrap(); + 2 + }; + + if true { + let _guard = m.lock(); + println!("foo"); + } else { + println!("foo"); + } + + if true { + let _guard = m.lock(); + println!("foo"); + println!("bar"); + } else { + let _guard = m.lock(); + println!("foo"); + println!("baz"); + } + + let mut c = 0; + for _ in 0..5 { + if c == 0 { + c += 1; + println!("0"); + } else if c == 1 { + c += 1; + println!("1"); + } else { + c += 1; + println!("more"); + } + } } diff --git a/tests/ui/cast_size_32bit.stderr b/tests/ui/cast_size_32bit.stderr index 7125f741c150..8990c3ba739b 100644 --- a/tests/ui/cast_size_32bit.stderr +++ b/tests/ui/cast_size_32bit.stderr @@ -114,5 +114,5 @@ LL | 3_999_999_999usize as f64; | = note: `-D clippy::unnecessary-cast` implied by `-D warnings` -error: aborting due to 20 previous errors +error: aborting due to 18 previous errors diff --git a/tests/ui/crashes/ice-9041.rs b/tests/ui/crashes/ice-9041.rs new file mode 100644 index 000000000000..55cc9bc99a0e --- /dev/null +++ b/tests/ui/crashes/ice-9041.rs @@ -0,0 +1,8 @@ +pub struct Thing; + +pub fn has_thing(things: &[Thing]) -> bool { + let is_thing_ready = |_peer: &Thing| -> bool { todo!() }; + things.iter().find(|p| is_thing_ready(p)).is_some() +} + +fn main() {} diff --git a/tests/ui/crashes/ice-9041.stderr b/tests/ui/crashes/ice-9041.stderr new file mode 100644 index 000000000000..f5038f0a8484 --- /dev/null +++ b/tests/ui/crashes/ice-9041.stderr @@ -0,0 +1,10 @@ +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/ice-9041.rs:5:19 + | +LL | things.iter().find(|p| is_thing_ready(p)).is_some() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|p| is_thing_ready(&p))` + | + = note: `-D clippy::search-is-some` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index 47bf25e409bd..687efdada6e3 100644 --- a/tests/ui/equatable_if_let.fixed +++ b/tests/ui/equatable_if_let.fixed @@ -1,8 +1,12 @@ // run-rustfix +// aux-build:macro_rules.rs #![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)] #![warn(clippy::equatable_if_let)] +#[macro_use] +extern crate macro_rules; + use std::cmp::Ordering; #[derive(PartialEq)] @@ -75,4 +79,6 @@ fn main() { if "abc" == m1!(x) { println!("OK"); } + + equatable_if_let!(a); } diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs index d498bca2455b..8c467d14d2a9 100644 --- a/tests/ui/equatable_if_let.rs +++ b/tests/ui/equatable_if_let.rs @@ -1,8 +1,12 @@ // run-rustfix +// aux-build:macro_rules.rs #![allow(unused_variables, dead_code, clippy::derive_partial_eq_without_eq)] #![warn(clippy::equatable_if_let)] +#[macro_use] +extern crate macro_rules; + use std::cmp::Ordering; #[derive(PartialEq)] @@ -75,4 +79,6 @@ fn main() { if let m1!(x) = "abc" { println!("OK"); } + + equatable_if_let!(a); } diff --git a/tests/ui/equatable_if_let.stderr b/tests/ui/equatable_if_let.stderr index 760ff88f448f..9c4c3cc3682e 100644 --- a/tests/ui/equatable_if_let.stderr +++ b/tests/ui/equatable_if_let.stderr @@ -1,5 +1,5 @@ error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:49:8 + --> $DIR/equatable_if_let.rs:53:8 | LL | if let 2 = a {} | ^^^^^^^^^ help: try: `a == 2` @@ -7,61 +7,61 @@ LL | if let 2 = a {} = note: `-D clippy::equatable-if-let` implied by `-D warnings` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:50:8 + --> $DIR/equatable_if_let.rs:54:8 | LL | if let Ordering::Greater = a.cmp(&b) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `a.cmp(&b) == Ordering::Greater` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:51:8 + --> $DIR/equatable_if_let.rs:55:8 | LL | if let Some(2) = c {} | ^^^^^^^^^^^^^^^ help: try: `c == Some(2)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:52:8 + --> $DIR/equatable_if_let.rs:56:8 | LL | if let Struct { a: 2, b: false } = d {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `d == (Struct { a: 2, b: false })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:53:8 + --> $DIR/equatable_if_let.rs:57:8 | LL | if let Enum::TupleVariant(32, 64) = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::TupleVariant(32, 64)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:54:8 + --> $DIR/equatable_if_let.rs:58:8 | LL | if let Enum::RecordVariant { a: 64, b: 32 } = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == (Enum::RecordVariant { a: 64, b: 32 })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:55:8 + --> $DIR/equatable_if_let.rs:59:8 | LL | if let Enum::UnitVariant = e {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `e == Enum::UnitVariant` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:56:8 + --> $DIR/equatable_if_let.rs:60:8 | LL | if let (Enum::UnitVariant, &Struct { a: 2, b: false }) = (e, &d) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(e, &d) == (Enum::UnitVariant, &Struct { a: 2, b: false })` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:66:8 + --> $DIR/equatable_if_let.rs:70:8 | LL | if let NotStructuralEq::A = g {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `g == NotStructuralEq::A` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:68:8 + --> $DIR/equatable_if_let.rs:72:8 | LL | if let Some(NotStructuralEq::A) = Some(g) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(g) == Some(NotStructuralEq::A)` error: this pattern matching can be expressed using equality - --> $DIR/equatable_if_let.rs:75:8 + --> $DIR/equatable_if_let.rs:79:8 | LL | if let m1!(x) = "abc" { | ^^^^^^^^^^^^^^^^^^ help: try: `"abc" == m1!(x)` diff --git a/tests/ui/explicit_auto_deref.fixed b/tests/ui/explicit_auto_deref.fixed index d4ff1b1566dc..a650fdc1f897 100644 --- a/tests/ui/explicit_auto_deref.fixed +++ b/tests/ui/explicit_auto_deref.fixed @@ -211,4 +211,8 @@ fn main() { unsafe { var(0, &**x); } + + let s = &"str"; + let _ = || return *s; + let _ = || -> &'static str { return s }; } diff --git a/tests/ui/explicit_auto_deref.rs b/tests/ui/explicit_auto_deref.rs index 99294a7947bf..8f4f352576a7 100644 --- a/tests/ui/explicit_auto_deref.rs +++ b/tests/ui/explicit_auto_deref.rs @@ -211,4 +211,8 @@ fn main() { unsafe { var(0, &**x); } + + let s = &"str"; + let _ = || return *s; + let _ = || -> &'static str { return *s }; } diff --git a/tests/ui/explicit_auto_deref.stderr b/tests/ui/explicit_auto_deref.stderr index 55f956e37aed..92765307ea73 100644 --- a/tests/ui/explicit_auto_deref.stderr +++ b/tests/ui/explicit_auto_deref.stderr @@ -192,5 +192,11 @@ error: deref which would be done by auto-deref LL | f_str(&&**ref_str); // `needless_borrow` will suggest removing only one reference | ^^^^^^^^^^ help: try this: `ref_str` -error: aborting due to 32 previous errors +error: deref which would be done by auto-deref + --> $DIR/explicit_auto_deref.rs:217:41 + | +LL | let _ = || -> &'static str { return *s }; + | ^^ help: try this: `s` + +error: aborting due to 33 previous errors diff --git a/tests/ui/if_let_mutex.rs b/tests/ui/if_let_mutex.rs index 58feae422a3c..6cbfafbb38b9 100644 --- a/tests/ui/if_let_mutex.rs +++ b/tests/ui/if_let_mutex.rs @@ -27,7 +27,7 @@ fn if_let_option() { }; } -// When mutexs are different don't warn +// When mutexes are different don't warn fn if_let_different_mutex() { let m = Mutex::new(Some(0_u8)); let other = Mutex::new(None::); diff --git a/tests/ui/inconsistent_struct_constructor.fixed b/tests/ui/inconsistent_struct_constructor.fixed index eb66d1afddce..74ba2f1c5e70 100644 --- a/tests/ui/inconsistent_struct_constructor.fixed +++ b/tests/ui/inconsistent_struct_constructor.fixed @@ -36,7 +36,7 @@ mod without_base { // issue #7069. new_foo!(); - // Shoule NOT lint because the order is the same as in the definition. + // Should NOT lint because the order is the same as in the definition. Foo { x, y, z }; // Should NOT lint because z is not a shorthand init. diff --git a/tests/ui/inconsistent_struct_constructor.rs b/tests/ui/inconsistent_struct_constructor.rs index 5caadc7c6208..ba96e1e330f5 100644 --- a/tests/ui/inconsistent_struct_constructor.rs +++ b/tests/ui/inconsistent_struct_constructor.rs @@ -36,7 +36,7 @@ mod without_base { // issue #7069. new_foo!(); - // Shoule NOT lint because the order is the same as in the definition. + // Should NOT lint because the order is the same as in the definition. Foo { x, y, z }; // Should NOT lint because z is not a shorthand init. diff --git a/tests/ui/invalid_utf8_in_unchecked.rs b/tests/ui/invalid_utf8_in_unchecked.rs new file mode 100644 index 000000000000..3dc096d3197f --- /dev/null +++ b/tests/ui/invalid_utf8_in_unchecked.rs @@ -0,0 +1,20 @@ +#![warn(clippy::invalid_utf8_in_unchecked)] + +fn main() { + // Valid + unsafe { + std::str::from_utf8_unchecked(&[99, 108, 105, 112, 112, 121]); + std::str::from_utf8_unchecked(&[b'c', b'l', b'i', b'p', b'p', b'y']); + std::str::from_utf8_unchecked(b"clippy"); + + let x = 0xA0; + std::str::from_utf8_unchecked(&[0xC0, x]); + } + + // Invalid + unsafe { + std::str::from_utf8_unchecked(&[99, 108, 130, 105, 112, 112, 121]); + std::str::from_utf8_unchecked(&[b'c', b'l', b'\x82', b'i', b'p', b'p', b'y']); + std::str::from_utf8_unchecked(b"cl\x82ippy"); + } +} diff --git a/tests/ui/invalid_utf8_in_unchecked.stderr b/tests/ui/invalid_utf8_in_unchecked.stderr new file mode 100644 index 000000000000..c89cd2758ee9 --- /dev/null +++ b/tests/ui/invalid_utf8_in_unchecked.stderr @@ -0,0 +1,22 @@ +error: non UTF-8 literal in `std::str::from_utf8_unchecked` + --> $DIR/invalid_utf8_in_unchecked.rs:16:9 + | +LL | std::str::from_utf8_unchecked(&[99, 108, 130, 105, 112, 112, 121]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::invalid-utf8-in-unchecked` implied by `-D warnings` + +error: non UTF-8 literal in `std::str::from_utf8_unchecked` + --> $DIR/invalid_utf8_in_unchecked.rs:17:9 + | +LL | std::str::from_utf8_unchecked(&[b'c', b'l', b'/x82', b'i', b'p', b'p', b'y']); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: non UTF-8 literal in `std::str::from_utf8_unchecked` + --> $DIR/invalid_utf8_in_unchecked.rs:18:9 + | +LL | std::str::from_utf8_unchecked(b"cl/x82ippy"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/let_unit.fixed b/tests/ui/let_unit.fixed index e72b74623255..6343cff0f7ff 100644 --- a/tests/ui/let_unit.fixed +++ b/tests/ui/let_unit.fixed @@ -1,8 +1,8 @@ // run-rustfix +#![feature(lint_reasons)] #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect)] -#![allow(unused_variables)] +#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -72,8 +72,8 @@ fn _returns_generic() { fn f3(x: T) -> T { x } - fn f4(mut x: Vec) -> T { - x.pop().unwrap() + fn f5(x: bool) -> Option { + x.then(|| T::default()) } let _: () = f(); // Ok @@ -85,8 +85,12 @@ fn _returns_generic() { f3(()); // Lint f3(()); // Lint - f4(vec![()]); // Lint - f4(vec![()]); // Lint + // Should lint: + // fn f4(mut x: Vec) -> T { + // x.pop().unwrap() + // } + // let _: () = f4(vec![()]); + // let x: () = f4(vec![()]); // Ok let _: () = { @@ -112,4 +116,62 @@ fn _returns_generic() { Some(1) => f2(3), Some(_) => (), }; + + let _: () = f5(true).unwrap(); + + #[allow(clippy::let_unit_value)] + { + let x = f(); + let y; + let z; + match 0 { + 0 => { + y = f(); + z = f(); + }, + 1 => { + println!("test"); + y = f(); + z = f3(()); + }, + _ => panic!(), + } + + let x1; + let x2; + if true { + x1 = f(); + x2 = x1; + } else { + x2 = f(); + x1 = x2; + } + + let opt; + match f5(true) { + Some(x) => opt = x, + None => panic!(), + }; + + #[warn(clippy::let_unit_value)] + { + let _: () = x; + let _: () = y; + z; + let _: () = x1; + let _: () = x2; + let _: () = opt; + } + } + + let () = f(); +} + +fn attributes() { + fn f() {} + + #[allow(clippy::let_unit_value)] + let _ = f(); + #[expect(clippy::let_unit_value)] + let _ = f(); } diff --git a/tests/ui/let_unit.rs b/tests/ui/let_unit.rs index 47ee0a767247..c9bb2849f5cf 100644 --- a/tests/ui/let_unit.rs +++ b/tests/ui/let_unit.rs @@ -1,8 +1,8 @@ // run-rustfix +#![feature(lint_reasons)] #![warn(clippy::let_unit_value)] -#![allow(clippy::no_effect)] -#![allow(unused_variables)] +#![allow(unused, clippy::no_effect, clippy::needless_late_init, path_statements)] macro_rules! let_and_return { ($n:expr) => {{ @@ -72,8 +72,8 @@ fn _returns_generic() { fn f3(x: T) -> T { x } - fn f4(mut x: Vec) -> T { - x.pop().unwrap() + fn f5(x: bool) -> Option { + x.then(|| T::default()) } let _: () = f(); // Ok @@ -85,8 +85,12 @@ fn _returns_generic() { let _: () = f3(()); // Lint let x: () = f3(()); // Lint - let _: () = f4(vec![()]); // Lint - let x: () = f4(vec![()]); // Lint + // Should lint: + // fn f4(mut x: Vec) -> T { + // x.pop().unwrap() + // } + // let _: () = f4(vec![()]); + // let x: () = f4(vec![()]); // Ok let _: () = { @@ -112,4 +116,62 @@ fn _returns_generic() { Some(1) => f2(3), Some(_) => (), }; + + let _: () = f5(true).unwrap(); + + #[allow(clippy::let_unit_value)] + { + let x = f(); + let y; + let z; + match 0 { + 0 => { + y = f(); + z = f(); + }, + 1 => { + println!("test"); + y = f(); + z = f3(()); + }, + _ => panic!(), + } + + let x1; + let x2; + if true { + x1 = f(); + x2 = x1; + } else { + x2 = f(); + x1 = x2; + } + + let opt; + match f5(true) { + Some(x) => opt = x, + None => panic!(), + }; + + #[warn(clippy::let_unit_value)] + { + let _: () = x; + let _: () = y; + let _: () = z; + let _: () = x1; + let _: () = x2; + let _: () = opt; + } + } + + let () = f(); +} + +fn attributes() { + fn f() {} + + #[allow(clippy::let_unit_value)] + let _ = f(); + #[expect(clippy::let_unit_value)] + let _ = f(); } diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 45bf67acdb73..49da74ca7e1c 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -64,19 +64,7 @@ LL | let x: () = f3(()); // Lint | ^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f3(());` error: this let-binding has unit value - --> $DIR/let_unit.rs:88:5 - | -LL | let _: () = f4(vec![()]); // Lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f4(vec![()]);` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:89:5 - | -LL | let x: () = f4(vec![()]); // Lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: omit the `let` binding: `f4(vec![()]);` - -error: this let-binding has unit value - --> $DIR/let_unit.rs:98:5 + --> $DIR/let_unit.rs:102:5 | LL | let x: () = if true { f() } else { f2(0) }; // Lint | ^^^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -84,7 +72,7 @@ LL | let x: () = if true { f() } else { f2(0) }; // Lint | help: use a wild (`_`) binding: `_` error: this let-binding has unit value - --> $DIR/let_unit.rs:109:5 + --> $DIR/let_unit.rs:113:5 | LL | / let _: () = match Some(0) { LL | | None => f2(1), @@ -104,5 +92,11 @@ LL + Some(_) => (), LL + }; | -error: aborting due to 11 previous errors +error: this let-binding has unit value + --> $DIR/let_unit.rs:160:13 + | +LL | let _: () = z; + | ^^^^^^^^^^^^^^ help: omit the `let` binding: `z;` + +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_filter_map.fixed b/tests/ui/manual_filter_map.fixed index de0d86148899..4936dc9b2e01 100644 --- a/tests/ui/manual_filter_map.fixed +++ b/tests/ui/manual_filter_map.fixed @@ -12,6 +12,32 @@ fn main() { // is_ok(), unwrap_or() let _ = (0..).filter_map(|a| to_res(a).ok()); + + let _ = (1..5) + .filter_map(|y| *to_ref(to_opt(y))); + let _ = (1..5) + .filter_map(|y| *to_ref(to_opt(y))); + + let _ = (1..5) + .filter_map(|y| to_ref(to_res(y)).ok()); + let _ = (1..5) + .filter_map(|y| to_ref(to_res(y)).ok()); +} + +#[rustfmt::skip] +fn simple_equal() { + iter::>().find_map(|x| x.cloned()); + iter::<&Option<&u8>>().find_map(|x| x.cloned()); + iter::<&Option>().find_map(|x| x.as_deref()); + iter::>().find_map(|y| to_ref(y).cloned()); + + iter::>().find_map(|x| x.ok()); + iter::<&Result>().find_map(|x| x.ok()); + iter::<&&Result>().find_map(|x| x.ok()); + iter::>().find_map(|x| x.cloned().ok()); + iter::<&Result<&u8, ()>>().find_map(|x| x.cloned().ok()); + iter::<&Result>().find_map(|x| x.as_deref().ok()); + iter::>().find_map(|y| to_ref(y).cloned().ok()); } fn no_lint() { @@ -28,6 +54,10 @@ fn no_lint() { .map(|a| to_opt(a).unwrap()); } +fn iter() -> impl Iterator { + std::iter::empty() +} + fn to_opt(_: T) -> Option { unimplemented!() } @@ -36,6 +66,10 @@ fn to_res(_: T) -> Result { unimplemented!() } +fn to_ref<'a, T>(_: T) -> &'a T { + unimplemented!() +} + struct Issue8920<'a> { option_field: Option, result_field: Result, diff --git a/tests/ui/manual_filter_map.rs b/tests/ui/manual_filter_map.rs index bd6516f038b2..8c67e827b4c3 100644 --- a/tests/ui/manual_filter_map.rs +++ b/tests/ui/manual_filter_map.rs @@ -12,6 +12,36 @@ fn main() { // is_ok(), unwrap_or() let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + + let _ = (1..5) + .filter(|&x| to_ref(to_opt(x)).is_some()) + .map(|y| to_ref(to_opt(y)).unwrap()); + let _ = (1..5) + .filter(|x| to_ref(to_opt(*x)).is_some()) + .map(|y| to_ref(to_opt(y)).unwrap()); + + let _ = (1..5) + .filter(|&x| to_ref(to_res(x)).is_ok()) + .map(|y| to_ref(to_res(y)).unwrap()); + let _ = (1..5) + .filter(|x| to_ref(to_res(*x)).is_ok()) + .map(|y| to_ref(to_res(y)).unwrap()); +} + +#[rustfmt::skip] +fn simple_equal() { + iter::>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + iter::<&Option>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); + iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + + iter::>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::<&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::<&&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + iter::<&Result>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); + iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); } fn no_lint() { @@ -28,6 +58,10 @@ fn no_lint() { .map(|a| to_opt(a).unwrap()); } +fn iter() -> impl Iterator { + std::iter::empty() +} + fn to_opt(_: T) -> Option { unimplemented!() } @@ -36,6 +70,10 @@ fn to_res(_: T) -> Result { unimplemented!() } +fn to_ref<'a, T>(_: T) -> &'a T { + unimplemented!() +} + struct Issue8920<'a> { option_field: Option, result_field: Result, diff --git a/tests/ui/manual_filter_map.stderr b/tests/ui/manual_filter_map.stderr index 465f1b191101..6e5bbe8f2aaf 100644 --- a/tests/ui/manual_filter_map.stderr +++ b/tests/ui/manual_filter_map.stderr @@ -19,7 +19,107 @@ LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_o | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:54:10 + --> $DIR/manual_filter_map.rs:17:10 + | +LL | .filter(|&x| to_ref(to_opt(x)).is_some()) + | __________^ +LL | | .map(|y| to_ref(to_opt(y)).unwrap()); + | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:20:10 + | +LL | .filter(|x| to_ref(to_opt(*x)).is_some()) + | __________^ +LL | | .map(|y| to_ref(to_opt(y)).unwrap()); + | |____________________________________________^ help: try: `filter_map(|y| *to_ref(to_opt(y)))` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:24:10 + | +LL | .filter(|&x| to_ref(to_res(x)).is_ok()) + | __________^ +LL | | .map(|y| to_ref(to_res(y)).unwrap()); + | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:27:10 + | +LL | .filter(|x| to_ref(to_res(*x)).is_ok()) + | __________^ +LL | | .map(|y| to_ref(to_res(y)).unwrap()); + | |____________________________________________^ help: try: `filter_map(|y| to_ref(to_res(y)).ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:33:27 + | +LL | iter::>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` + | + = note: `-D clippy::manual-find-map` implied by `-D warnings` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:34:28 + | +LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:35:31 + | +LL | iter::<&Option>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:36:31 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:38:30 + | +LL | iter::>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:39:31 + | +LL | iter::<&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:40:32 + | +LL | iter::<&&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:41:31 + | +LL | iter::>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:42:32 + | +LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:43:35 + | +LL | iter::<&Result>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_filter_map.rs:44:35 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:92:10 | LL | .filter(|f| f.option_field.is_some()) | __________^ @@ -27,7 +127,7 @@ LL | | .map(|f| f.option_field.clone().unwrap()); | |_________________________________________________^ help: try: `filter_map(|f| f.option_field.clone())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:59:10 + --> $DIR/manual_filter_map.rs:97:10 | LL | .filter(|f| f.ref_field.is_some()) | __________^ @@ -35,7 +135,7 @@ LL | | .map(|f| f.ref_field.cloned().unwrap()); | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.cloned())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:64:10 + --> $DIR/manual_filter_map.rs:102:10 | LL | .filter(|f| f.ref_field.is_some()) | __________^ @@ -43,7 +143,7 @@ LL | | .map(|f| f.ref_field.copied().unwrap()); | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.copied())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:69:10 + --> $DIR/manual_filter_map.rs:107:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ @@ -51,7 +151,7 @@ LL | | .map(|f| f.result_field.clone().unwrap()); | |_________________________________________________^ help: try: `filter_map(|f| f.result_field.clone().ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:74:10 + --> $DIR/manual_filter_map.rs:112:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ @@ -59,7 +159,7 @@ LL | | .map(|f| f.result_field.as_ref().unwrap()); | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_ref().ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:79:10 + --> $DIR/manual_filter_map.rs:117:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ @@ -67,7 +167,7 @@ LL | | .map(|f| f.result_field.as_deref().unwrap()); | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref().ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:84:10 + --> $DIR/manual_filter_map.rs:122:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ @@ -75,7 +175,7 @@ LL | | .map(|f| f.result_field.as_mut().unwrap()); | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_mut().ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:89:10 + --> $DIR/manual_filter_map.rs:127:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ @@ -83,12 +183,12 @@ LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); | |________________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref_mut().ok())` error: `filter(..).map(..)` can be simplified as `filter_map(..)` - --> $DIR/manual_filter_map.rs:94:10 + --> $DIR/manual_filter_map.rs:132:10 | LL | .filter(|f| f.result_field.is_ok()) | __________^ LL | | .map(|f| f.result_field.to_owned().unwrap()); | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` -error: aborting due to 12 previous errors +error: aborting due to 27 previous errors diff --git a/tests/ui/manual_find_map.fixed b/tests/ui/manual_find_map.fixed index d69b6c1dcf3b..54302beceff6 100644 --- a/tests/ui/manual_find_map.fixed +++ b/tests/ui/manual_find_map.fixed @@ -12,6 +12,35 @@ fn main() { // is_ok(), unwrap_or() let _ = (0..).find_map(|a| to_res(a).ok()); + + let _ = (1..5) + .find_map(|y| *to_ref(to_opt(y))); + let _ = (1..5) + .find_map(|y| *to_ref(to_opt(y))); + + let _ = (1..5) + .find_map(|y| to_ref(to_res(y)).ok()); + let _ = (1..5) + .find_map(|y| to_ref(to_res(y)).ok()); +} + +#[rustfmt::skip] +fn simple_equal() { + iter::>().find_map(|x| x); + iter::<&Option>().find_map(|x| *x); + iter::<&&Option>().find_map(|x| **x); + iter::>().find_map(|x| x.cloned()); + iter::<&Option<&u8>>().find_map(|x| x.cloned()); + iter::<&Option>().find_map(|x| x.as_deref()); + iter::>().find_map(|y| to_ref(y).cloned()); + + iter::>().find_map(|x| x.ok()); + iter::<&Result>().find_map(|x| x.ok()); + iter::<&&Result>().find_map(|x| x.ok()); + iter::>().find_map(|x| x.cloned().ok()); + iter::<&Result<&u8, ()>>().find_map(|x| x.cloned().ok()); + iter::<&Result>().find_map(|x| x.as_deref().ok()); + iter::>().find_map(|y| to_ref(y).cloned().ok()); } fn no_lint() { @@ -28,6 +57,10 @@ fn no_lint() { .map(|a| to_opt(a).unwrap()); } +fn iter() -> impl Iterator { + std::iter::empty() +} + fn to_opt(_: T) -> Option { unimplemented!() } @@ -36,6 +69,10 @@ fn to_res(_: T) -> Result { unimplemented!() } +fn to_ref<'a, T>(_: T) -> &'a T { + unimplemented!() +} + struct Issue8920<'a> { option_field: Option, result_field: Result, diff --git a/tests/ui/manual_find_map.rs b/tests/ui/manual_find_map.rs index 1c4e18e31c8b..afcc1825a9ac 100644 --- a/tests/ui/manual_find_map.rs +++ b/tests/ui/manual_find_map.rs @@ -12,6 +12,39 @@ fn main() { // is_ok(), unwrap_or() let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); + + let _ = (1..5) + .find(|&x| to_ref(to_opt(x)).is_some()) + .map(|y| to_ref(to_opt(y)).unwrap()); + let _ = (1..5) + .find(|x| to_ref(to_opt(*x)).is_some()) + .map(|y| to_ref(to_opt(y)).unwrap()); + + let _ = (1..5) + .find(|&x| to_ref(to_res(x)).is_ok()) + .map(|y| to_ref(to_res(y)).unwrap()); + let _ = (1..5) + .find(|x| to_ref(to_res(*x)).is_ok()) + .map(|y| to_ref(to_res(y)).unwrap()); +} + +#[rustfmt::skip] +fn simple_equal() { + iter::>().find(|x| x.is_some()).map(|x| x.unwrap()); + iter::<&Option>().find(|x| x.is_some()).map(|x| x.unwrap()); + iter::<&&Option>().find(|x| x.is_some()).map(|x| x.unwrap()); + iter::>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + iter::<&Option>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); + iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + + iter::>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::<&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::<&&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + iter::>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + iter::<&Result>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); + iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); } fn no_lint() { @@ -28,6 +61,10 @@ fn no_lint() { .map(|a| to_opt(a).unwrap()); } +fn iter() -> impl Iterator { + std::iter::empty() +} + fn to_opt(_: T) -> Option { unimplemented!() } @@ -36,6 +73,10 @@ fn to_res(_: T) -> Result { unimplemented!() } +fn to_ref<'a, T>(_: T) -> &'a T { + unimplemented!() +} + struct Issue8920<'a> { option_field: Option, result_field: Result, diff --git a/tests/ui/manual_find_map.stderr b/tests/ui/manual_find_map.stderr index 9dea42b76868..c1ac499f7c60 100644 --- a/tests/ui/manual_find_map.stderr +++ b/tests/ui/manual_find_map.stderr @@ -19,7 +19,123 @@ LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or( | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:54:10 + --> $DIR/manual_find_map.rs:17:10 + | +LL | .find(|&x| to_ref(to_opt(x)).is_some()) + | __________^ +LL | | .map(|y| to_ref(to_opt(y)).unwrap()); + | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:20:10 + | +LL | .find(|x| to_ref(to_opt(*x)).is_some()) + | __________^ +LL | | .map(|y| to_ref(to_opt(y)).unwrap()); + | |____________________________________________^ help: try: `find_map(|y| *to_ref(to_opt(y)))` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:24:10 + | +LL | .find(|&x| to_ref(to_res(x)).is_ok()) + | __________^ +LL | | .map(|y| to_ref(to_res(y)).unwrap()); + | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:27:10 + | +LL | .find(|x| to_ref(to_res(*x)).is_ok()) + | __________^ +LL | | .map(|y| to_ref(to_res(y)).unwrap()); + | |____________________________________________^ help: try: `find_map(|y| to_ref(to_res(y)).ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:33:26 + | +LL | iter::>().find(|x| x.is_some()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x)` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:34:27 + | +LL | iter::<&Option>().find(|x| x.is_some()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| *x)` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:35:28 + | +LL | iter::<&&Option>().find(|x| x.is_some()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| **x)` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:36:27 + | +LL | iter::>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:37:28 + | +LL | iter::<&Option<&u8>>().find(|x| x.is_some()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:38:31 + | +LL | iter::<&Option>().find(|x| x.is_some()).map(|x| x.as_deref().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:39:31 + | +LL | iter::>().find(|&x| to_ref(x).is_some()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:41:30 + | +LL | iter::>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:42:31 + | +LL | iter::<&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:43:32 + | +LL | iter::<&&Result>().find(|x| x.is_ok()).map(|x| x.unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:44:31 + | +LL | iter::>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:45:32 + | +LL | iter::<&Result<&u8, ()>>().find(|x| x.is_ok()).map(|x| x.cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.cloned().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:46:35 + | +LL | iter::<&Result>().find(|x| x.is_ok()).map(|x| x.as_deref().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|x| x.as_deref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:47:35 + | +LL | iter::>().find(|&x| to_ref(x).is_ok()).map(|y| to_ref(y).cloned().unwrap()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|y| to_ref(y).cloned().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:95:10 | LL | .find(|f| f.option_field.is_some()) | __________^ @@ -27,7 +143,7 @@ LL | | .map(|f| f.option_field.clone().unwrap()); | |_________________________________________________^ help: try: `find_map(|f| f.option_field.clone())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:59:10 + --> $DIR/manual_find_map.rs:100:10 | LL | .find(|f| f.ref_field.is_some()) | __________^ @@ -35,7 +151,7 @@ LL | | .map(|f| f.ref_field.cloned().unwrap()); | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.cloned())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:64:10 + --> $DIR/manual_find_map.rs:105:10 | LL | .find(|f| f.ref_field.is_some()) | __________^ @@ -43,7 +159,7 @@ LL | | .map(|f| f.ref_field.copied().unwrap()); | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.copied())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:69:10 + --> $DIR/manual_find_map.rs:110:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ @@ -51,7 +167,7 @@ LL | | .map(|f| f.result_field.clone().unwrap()); | |_________________________________________________^ help: try: `find_map(|f| f.result_field.clone().ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:74:10 + --> $DIR/manual_find_map.rs:115:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ @@ -59,7 +175,7 @@ LL | | .map(|f| f.result_field.as_ref().unwrap()); | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_ref().ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:79:10 + --> $DIR/manual_find_map.rs:120:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ @@ -67,7 +183,7 @@ LL | | .map(|f| f.result_field.as_deref().unwrap()); | |____________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref().ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:84:10 + --> $DIR/manual_find_map.rs:125:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ @@ -75,7 +191,7 @@ LL | | .map(|f| f.result_field.as_mut().unwrap()); | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_mut().ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:89:10 + --> $DIR/manual_find_map.rs:130:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ @@ -83,12 +199,12 @@ LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); | |________________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref_mut().ok())` error: `find(..).map(..)` can be simplified as `find_map(..)` - --> $DIR/manual_find_map.rs:94:10 + --> $DIR/manual_find_map.rs:135:10 | LL | .find(|f| f.result_field.is_ok()) | __________^ LL | | .map(|f| f.result_field.to_owned().unwrap()); | |____________________________________________________^ help: try: `find_map(|f| f.result_field.to_owned().ok())` -error: aborting due to 12 previous errors +error: aborting due to 30 previous errors diff --git a/tests/ui/manual_flatten.rs b/tests/ui/manual_flatten.rs index 6c5232ec5f55..d922593bc6f9 100644 --- a/tests/ui/manual_flatten.rs +++ b/tests/ui/manual_flatten.rs @@ -106,4 +106,20 @@ fn main() { for n in vec![Some(1), Some(2), Some(3)].iter().flatten() { println!("{}", n); } + + run_unformatted_tests(); +} + +#[rustfmt::skip] +fn run_unformatted_tests() { + // Skip rustfmt here on purpose so the suggestion does not fit in one line + for n in vec![ + Some(1), + Some(2), + Some(3) + ].iter() { + if let Some(n) = n { + println!("{:?}", n); + } + } } diff --git a/tests/ui/manual_flatten.stderr b/tests/ui/manual_flatten.stderr index 392e1a393937..da053c056683 100644 --- a/tests/ui/manual_flatten.stderr +++ b/tests/ui/manual_flatten.stderr @@ -167,5 +167,33 @@ LL | | println!("{:?}", n); LL | | } | |_________^ -error: aborting due to 8 previous errors +error: unnecessary `if let` since only the `Some` variant of the iterator element is used + --> $DIR/manual_flatten.rs:116:5 + | +LL | / for n in vec![ +LL | | Some(1), +LL | | Some(2), +LL | | Some(3) +... | +LL | | } +LL | | } + | |_____^ + | +help: remove the `if let` statement in the for loop and then... + --> $DIR/manual_flatten.rs:121:9 + | +LL | / if let Some(n) = n { +LL | | println!("{:?}", n); +LL | | } + | |_________^ +help: try + | +LL ~ for n in vec![ +LL + Some(1), +LL + Some(2), +LL + Some(3) +LL ~ ].iter().flatten() { + | + +error: aborting due to 9 previous errors diff --git a/tests/ui/map_flatten_fixable.fixed b/tests/ui/map_flatten_fixable.fixed index e9b41354c58f..312819a0a2cf 100644 --- a/tests/ui/map_flatten_fixable.fixed +++ b/tests/ui/map_flatten_fixable.fixed @@ -34,21 +34,20 @@ fn main() { } fn issue8734() { - // let _ = [0u8, 1, 2, 3] - // .into_iter() - // .map(|n| match n { - // 1 => [n - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1)], - // n => [n], - // }) - // .flatten(); + let _ = [0u8, 1, 2, 3] + .into_iter() + .flat_map(|n| match n { + 1 => [n + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1)], + n => [n], + }); } #[allow(clippy::bind_instead_of_map)] // map + flatten will be suggested to `and_then`, but afterwards `map` is suggested again @@ -59,7 +58,7 @@ fn issue8878() { .and_then(|_| { // we need some newlines // so that the span is big enough -// for a splitted output of the diagnostic +// for a split output of the diagnostic Some("") // whitespace beforehand is important as well }); diff --git a/tests/ui/map_flatten_fixable.rs b/tests/ui/map_flatten_fixable.rs index 4345c6eee746..3fbf4f9a1b04 100644 --- a/tests/ui/map_flatten_fixable.rs +++ b/tests/ui/map_flatten_fixable.rs @@ -34,21 +34,21 @@ fn main() { } fn issue8734() { - // let _ = [0u8, 1, 2, 3] - // .into_iter() - // .map(|n| match n { - // 1 => [n - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1) - // .saturating_add(1)], - // n => [n], - // }) - // .flatten(); + let _ = [0u8, 1, 2, 3] + .into_iter() + .map(|n| match n { + 1 => [n + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1) + .saturating_add(1)], + n => [n], + }) + .flatten(); } #[allow(clippy::bind_instead_of_map)] // map + flatten will be suggested to `and_then`, but afterwards `map` is suggested again @@ -59,7 +59,7 @@ fn issue8878() { .map(|_| { // we need some newlines // so that the span is big enough -// for a splitted output of the diagnostic +// for a split output of the diagnostic Some("") // whitespace beforehand is important as well }) diff --git a/tests/ui/map_flatten_fixable.stderr b/tests/ui/map_flatten_fixable.stderr index f3b82ad08d0f..c91f0b9ae94f 100644 --- a/tests/ui/map_flatten_fixable.stderr +++ b/tests/ui/map_flatten_fixable.stderr @@ -42,6 +42,35 @@ error: called `map(..).flatten()` on `Result` LL | let _: Result<_, &str> = (Ok(Ok(1))).map(|x| x).flatten(); | ^^^^^^^^^^^^^^^^^^^^ help: try replacing `map` with `and_then` and remove the `.flatten()`: `and_then(|x| x)` +error: called `map(..).flatten()` on `Iterator` + --> $DIR/map_flatten_fixable.rs:39:10 + | +LL | .map(|n| match n { + | __________^ +LL | | 1 => [n +LL | | .saturating_add(1) +LL | | .saturating_add(1) +... | +LL | | }) +LL | | .flatten(); + | |__________________^ + | +help: try replacing `map` with `flat_map` and remove the `.flatten()` + | +LL ~ .flat_map(|n| match n { +LL + 1 => [n +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1) +LL + .saturating_add(1)], +LL + n => [n], +LL ~ }); + | + error: called `map(..).flatten()` on `Option` --> $DIR/map_flatten_fixable.rs:59:10 | @@ -49,7 +78,7 @@ LL | .map(|_| { | __________^ LL | | // we need some newlines LL | | // so that the span is big enough -LL | | // for a splitted output of the diagnostic +LL | | // for a split output of the diagnostic ... | LL | | }) LL | | .flatten(); @@ -60,11 +89,11 @@ help: try replacing `map` with `and_then` and remove the `.flatten()` LL ~ .and_then(|_| { LL + // we need some newlines LL + // so that the span is big enough -LL + // for a splitted output of the diagnostic +LL + // for a split output of the diagnostic LL + Some("") LL + // whitespace beforehand is important as well LL ~ }); | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/match_expr_like_matches_macro.fixed b/tests/ui/match_expr_like_matches_macro.fixed index 36f233f33460..1ccbfda64b73 100644 --- a/tests/ui/match_expr_like_matches_macro.fixed +++ b/tests/ui/match_expr_like_matches_macro.fixed @@ -45,6 +45,12 @@ fn main() { // lint let _ans = matches!(x, E::A(_) | E::B(_)); } + { + // lint + // skip rustfmt to prevent removing block for first pattern + #[rustfmt::skip] + let _ans = matches!(x, E::A(_) | E::B(_)); + } { // lint let _ans = !matches!(x, E::B(_) | E::C); diff --git a/tests/ui/match_expr_like_matches_macro.rs b/tests/ui/match_expr_like_matches_macro.rs index 750f69fa5088..a49991f59417 100644 --- a/tests/ui/match_expr_like_matches_macro.rs +++ b/tests/ui/match_expr_like_matches_macro.rs @@ -61,6 +61,18 @@ fn main() { _ => false, }; } + { + // lint + // skip rustfmt to prevent removing block for first pattern + #[rustfmt::skip] + let _ans = match x { + E::A(_) => { + true + } + E::B(_) => true, + _ => false, + }; + } { // lint let _ans = match x { diff --git a/tests/ui/match_expr_like_matches_macro.stderr b/tests/ui/match_expr_like_matches_macro.stderr index d7cedf9f9f15..e94555e27448 100644 --- a/tests/ui/match_expr_like_matches_macro.stderr +++ b/tests/ui/match_expr_like_matches_macro.stderr @@ -60,7 +60,20 @@ LL | | }; | |_________^ help: try this: `matches!(x, E::A(_) | E::B(_))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:66:20 + --> $DIR/match_expr_like_matches_macro.rs:68:20 + | +LL | let _ans = match x { + | ____________________^ +LL | | E::A(_) => { +LL | | true +LL | | } +LL | | E::B(_) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(x, E::A(_) | E::B(_))` + +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:78:20 | LL | let _ans = match x { | ____________________^ @@ -71,7 +84,7 @@ LL | | }; | |_________^ help: try this: `!matches!(x, E::B(_) | E::C)` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:126:18 + --> $DIR/match_expr_like_matches_macro.rs:138:18 | LL | let _z = match &z { | __________________^ @@ -81,7 +94,7 @@ LL | | }; | |_________^ help: try this: `matches!(z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:135:18 + --> $DIR/match_expr_like_matches_macro.rs:147:18 | LL | let _z = match &z { | __________________^ @@ -91,7 +104,7 @@ LL | | }; | |_________^ help: try this: `matches!(&z, Some(3))` error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:152:21 + --> $DIR/match_expr_like_matches_macro.rs:164:21 | LL | let _ = match &z { | _____________________^ @@ -100,16 +113,6 @@ LL | | _ => false, LL | | }; | |_____________^ help: try this: `matches!(&z, AnEnum::X)` -error: match expression looks like `matches!` macro - --> $DIR/match_expr_like_matches_macro.rs:166:20 - | -LL | let _res = match &val { - | ____________________^ -LL | | &Some(ref _a) => true, -LL | | _ => false, -LL | | }; - | |_________^ help: try this: `matches!(&val, &Some(ref _a))` - error: match expression looks like `matches!` macro --> $DIR/match_expr_like_matches_macro.rs:178:20 | @@ -120,5 +123,15 @@ LL | | _ => false, LL | | }; | |_________^ help: try this: `matches!(&val, &Some(ref _a))` -error: aborting due to 12 previous errors +error: match expression looks like `matches!` macro + --> $DIR/match_expr_like_matches_macro.rs:190:20 + | +LL | let _res = match &val { + | ____________________^ +LL | | &Some(ref _a) => true, +LL | | _ => false, +LL | | }; + | |_________^ help: try this: `matches!(&val, &Some(ref _a))` + +error: aborting due to 13 previous errors diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index cb0051224360..09afe2ddbbf6 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -115,6 +115,18 @@ fn main() { fn foo_ref(&self) {} } (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` + + struct S; + impl From for u32 { + fn from(s: S) -> Self { + (&s).into() + } + } + impl From<&S> for u32 { + fn from(s: &S) -> Self { + 0 + } + } } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index d636a4010037..3ae4722a1f89 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -115,6 +115,18 @@ fn main() { fn foo_ref(&self) {} } (&&()).foo_ref(); // Don't lint. `&()` will call `<() as FooRef>::foo_ref` + + struct S; + impl From for u32 { + fn from(s: S) -> Self { + (&s).into() + } + } + impl From<&S> for u32 { + fn from(s: &S) -> Self { + 0 + } + } } #[allow(clippy::needless_borrowed_reference)] diff --git a/tests/ui/needless_match.fixed b/tests/ui/needless_match.fixed index b997e5316cf3..0c9178fb85ef 100644 --- a/tests/ui/needless_match.fixed +++ b/tests/ui/needless_match.fixed @@ -99,6 +99,7 @@ fn if_let_result() { let _: Result = x; let _: Result = x; // Input type mismatch, don't trigger + #[allow(clippy::question_mark)] let _: Result = if let Err(e) = Ok(1) { Err(e) } else { x }; } diff --git a/tests/ui/needless_match.rs b/tests/ui/needless_match.rs index 90482775a1ee..f66f01d7ccaf 100644 --- a/tests/ui/needless_match.rs +++ b/tests/ui/needless_match.rs @@ -122,6 +122,7 @@ fn if_let_result() { let _: Result = if let Err(e) = x { Err(e) } else { x }; let _: Result = if let Ok(val) = x { Ok(val) } else { x }; // Input type mismatch, don't trigger + #[allow(clippy::question_mark)] let _: Result = if let Err(e) = Ok(1) { Err(e) } else { x }; } diff --git a/tests/ui/needless_match.stderr b/tests/ui/needless_match.stderr index 2d679631c6f0..5bc79800a1a7 100644 --- a/tests/ui/needless_match.stderr +++ b/tests/ui/needless_match.stderr @@ -84,7 +84,7 @@ LL | let _: Result = if let Ok(val) = x { Ok(val) } else { x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x` error: this if-let expression is unnecessary - --> $DIR/needless_match.rs:129:21 + --> $DIR/needless_match.rs:130:21 | LL | let _: Simple = if let Simple::A = x { | _____________________^ @@ -97,7 +97,7 @@ LL | | }; | |_____^ help: replace it with: `x` error: this match expression is unnecessary - --> $DIR/needless_match.rs:168:26 + --> $DIR/needless_match.rs:169:26 | LL | let _: Complex = match ce { | __________________________^ diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index 538927b18055..65809023f8df 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -212,3 +212,17 @@ impl DocHidden { } fn main() {} + +pub struct IgnoreConstGenericNew(usize); +impl IgnoreConstGenericNew { + pub fn new() -> Self { + Self(N) + } +} + +pub struct IgnoreLifetimeNew; +impl IgnoreLifetimeNew { + pub fn new<'a>() -> Self { + Self + } +} diff --git a/tests/ui/no_effect.rs b/tests/ui/no_effect.rs index f0c59b4080be..fdefb11ae17a 100644 --- a/tests/ui/no_effect.rs +++ b/tests/ui/no_effect.rs @@ -5,7 +5,6 @@ #![allow(clippy::deref_addrof)] #![allow(clippy::redundant_field_names)] - struct Unit; struct Tuple(i32); struct Struct { diff --git a/tests/ui/no_effect.stderr b/tests/ui/no_effect.stderr index 06b88bb5bee7..328d2555ceb8 100644 --- a/tests/ui/no_effect.stderr +++ b/tests/ui/no_effect.stderr @@ -1,5 +1,5 @@ error: statement with no effect - --> $DIR/no_effect.rs:95:5 + --> $DIR/no_effect.rs:94:5 | LL | 0; | ^^ @@ -7,157 +7,157 @@ LL | 0; = note: `-D clippy::no-effect` implied by `-D warnings` error: statement with no effect - --> $DIR/no_effect.rs:96:5 + --> $DIR/no_effect.rs:95:5 | LL | s2; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:97:5 + --> $DIR/no_effect.rs:96:5 | LL | Unit; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:98:5 + --> $DIR/no_effect.rs:97:5 | LL | Tuple(0); | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:99:5 + --> $DIR/no_effect.rs:98:5 | LL | Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:100:5 + --> $DIR/no_effect.rs:99:5 | LL | Struct { ..s }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:101:5 + --> $DIR/no_effect.rs:100:5 | LL | Union { a: 0 }; | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:102:5 + --> $DIR/no_effect.rs:101:5 | LL | Enum::Tuple(0); | ^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:103:5 + --> $DIR/no_effect.rs:102:5 | LL | Enum::Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:104:5 + --> $DIR/no_effect.rs:103:5 | LL | 5 + 6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:105:5 + --> $DIR/no_effect.rs:104:5 | LL | *&42; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:106:5 + --> $DIR/no_effect.rs:105:5 | LL | &6; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:107:5 + --> $DIR/no_effect.rs:106:5 | LL | (5, 6, 7); | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:108:5 + --> $DIR/no_effect.rs:107:5 | LL | box 42; | ^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:109:5 + --> $DIR/no_effect.rs:108:5 | LL | ..; | ^^^ error: statement with no effect - --> $DIR/no_effect.rs:110:5 + --> $DIR/no_effect.rs:109:5 | LL | 5..; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:111:5 + --> $DIR/no_effect.rs:110:5 | LL | ..5; | ^^^^ error: statement with no effect - --> $DIR/no_effect.rs:112:5 + --> $DIR/no_effect.rs:111:5 | LL | 5..6; | ^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:113:5 + --> $DIR/no_effect.rs:112:5 | LL | 5..=6; | ^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:114:5 + --> $DIR/no_effect.rs:113:5 | LL | [42, 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:115:5 + --> $DIR/no_effect.rs:114:5 | LL | [42, 55][1]; | ^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:116:5 + --> $DIR/no_effect.rs:115:5 | LL | (42, 55).1; | ^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:117:5 + --> $DIR/no_effect.rs:116:5 | LL | [42; 55]; | ^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:118:5 + --> $DIR/no_effect.rs:117:5 | LL | [42; 55][13]; | ^^^^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:120:5 + --> $DIR/no_effect.rs:119:5 | LL | || x += 5; | ^^^^^^^^^^ error: statement with no effect - --> $DIR/no_effect.rs:122:5 + --> $DIR/no_effect.rs:121:5 | LL | FooString { s: s }; | ^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:123:5 + --> $DIR/no_effect.rs:122:5 | LL | let _unused = 1; | ^^^^^^^^^^^^^^^^ @@ -165,19 +165,19 @@ LL | let _unused = 1; = note: `-D clippy::no-effect-underscore-binding` implied by `-D warnings` error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:124:5 + --> $DIR/no_effect.rs:123:5 | LL | let _penguin = || println!("Some helpful closure"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:125:5 + --> $DIR/no_effect.rs:124:5 | LL | let _duck = Struct { field: 0 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: binding to `_` prefixed variable with no side-effect - --> $DIR/no_effect.rs:126:5 + --> $DIR/no_effect.rs:125:5 | LL | let _cat = [2, 4, 6, 8][2]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/or_fun_call.fixed b/tests/ui/or_fun_call.fixed index 123aed40251e..fdb08d953ff1 100644 --- a/tests/ui/or_fun_call.fixed +++ b/tests/ui/or_fun_call.fixed @@ -185,7 +185,8 @@ mod issue8239 { .reduce(|mut acc, f| { acc.push_str(&f); acc - }).unwrap_or_default(); + }) + .unwrap_or_default(); } fn more_to_max_suggestion_highest_lines_1() { @@ -197,7 +198,8 @@ mod issue8239 { let _ = ""; acc.push_str(&f); acc - }).unwrap_or_default(); + }) + .unwrap_or_default(); } fn equal_to_max_suggestion_highest_lines() { @@ -208,7 +210,8 @@ mod issue8239 { let _ = ""; acc.push_str(&f); acc - }).unwrap_or_default(); + }) + .unwrap_or_default(); } fn less_than_max_suggestion_highest_lines() { @@ -218,7 +221,8 @@ mod issue8239 { map.reduce(|mut acc, f| { acc.push_str(&f); acc - }).unwrap_or_default(); + }) + .unwrap_or_default(); } } diff --git a/tests/ui/or_fun_call.stderr b/tests/ui/or_fun_call.stderr index dfe15654bc32..4c5938ab88b9 100644 --- a/tests/ui/or_fun_call.stderr +++ b/tests/ui/or_fun_call.stderr @@ -7,10 +7,10 @@ LL | with_constructor.unwrap_or(make()); = note: `-D clippy::or-fun-call` implied by `-D warnings` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:52:5 + --> $DIR/or_fun_call.rs:52:14 | LL | with_new.unwrap_or(Vec::new()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_new.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:55:21 @@ -31,16 +31,16 @@ LL | with_err_args.unwrap_or(Vec::with_capacity(12)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|_| Vec::with_capacity(12))` error: use of `unwrap_or` followed by a call to `default` - --> $DIR/or_fun_call.rs:64:5 + --> $DIR/or_fun_call.rs:64:24 | LL | with_default_trait.unwrap_or(Default::default()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_trait.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a call to `default` - --> $DIR/or_fun_call.rs:67:5 + --> $DIR/or_fun_call.rs:67:23 | LL | with_default_type.unwrap_or(u64::default()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_default_type.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:70:18 @@ -49,16 +49,16 @@ LL | self_default.unwrap_or(::default()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(::default)` error: use of `unwrap_or` followed by a call to `default` - --> $DIR/or_fun_call.rs:73:5 + --> $DIR/or_fun_call.rs:73:18 | LL | real_default.unwrap_or(::default()); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `real_default.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:76:5 + --> $DIR/or_fun_call.rs:76:14 | LL | with_vec.unwrap_or(vec![]); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `with_vec.unwrap_or_default()` + | ^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a function call --> $DIR/or_fun_call.rs:79:21 @@ -109,90 +109,28 @@ LL | None.unwrap_or( unsafe { ptr_to_ref(s) } ); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_else(|| unsafe { ptr_to_ref(s) })` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:182:9 - | -LL | / frames -LL | | .iter() -LL | | .map(|f: &String| f.to_lowercase()) -LL | | .reduce(|mut acc, f| { -... | -LL | | }) -LL | | .unwrap_or(String::new()); - | |_____________________________________^ - | -help: try this - | -LL ~ frames -LL + .iter() -LL + .map(|f: &String| f.to_lowercase()) -LL + .reduce(|mut acc, f| { -LL + acc.push_str(&f); -LL + acc -LL ~ }).unwrap_or_default(); + --> $DIR/or_fun_call.rs:189:14 | +LL | .unwrap_or(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:195:9 - | -LL | / iter.map(|f: &String| f.to_lowercase()) -LL | | .reduce(|mut acc, f| { -LL | | let _ = ""; -LL | | let _ = ""; -... | -LL | | }) -LL | | .unwrap_or(String::new()); - | |_____________________________________^ - | -help: try this - | -LL ~ iter.map(|f: &String| f.to_lowercase()) -LL + .reduce(|mut acc, f| { -LL + let _ = ""; -LL + let _ = ""; -LL + acc.push_str(&f); -LL + acc -LL ~ }).unwrap_or_default(); + --> $DIR/or_fun_call.rs:202:14 | +LL | .unwrap_or(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:208:9 - | -LL | / iter.map(|f: &String| f.to_lowercase()) -LL | | .reduce(|mut acc, f| { -LL | | let _ = ""; -LL | | acc.push_str(&f); -LL | | acc -LL | | }) -LL | | .unwrap_or(String::new()); - | |_____________________________________^ - | -help: try this - | -LL ~ iter.map(|f: &String| f.to_lowercase()) -LL + .reduce(|mut acc, f| { -LL + let _ = ""; -LL + acc.push_str(&f); -LL + acc -LL ~ }).unwrap_or_default(); + --> $DIR/or_fun_call.rs:214:14 | +LL | .unwrap_or(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: use of `unwrap_or` followed by a call to `new` - --> $DIR/or_fun_call.rs:221:9 - | -LL | / map.reduce(|mut acc, f| { -LL | | acc.push_str(&f); -LL | | acc -LL | | }) -LL | | .unwrap_or(String::new()); - | |_________________________________^ - | -help: try this - | -LL ~ map.reduce(|mut acc, f| { -LL + acc.push_str(&f); -LL + acc -LL ~ }).unwrap_or_default(); + --> $DIR/or_fun_call.rs:225:10 | +LL | .unwrap_or(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `unwrap_or_default()` error: aborting due to 22 previous errors diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 13ce0f32d4bb..c4c9c8214333 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -1,5 +1,6 @@ // run-rustfix #![allow(unreachable_code)] +#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] fn some_func(a: Option) -> Option { @@ -154,26 +155,56 @@ fn f() -> NotOption { NotOption::First } -fn main() { - some_func(Some(42)); - some_func(None); - some_other_func(Some(42)); +fn do_something() {} - let copy_struct = CopyStruct { opt: Some(54) }; - copy_struct.func(); - - let move_struct = MoveStruct { - opt: Some(vec![42, 1337]), - }; - move_struct.ref_func(); - move_struct.clone().mov_func_reuse(); - move_struct.mov_func_no_use(); - - let so = SeemsOption::Some(45); - returns_something_similar_to_option(so); - - func(); - - let _ = result_func(Ok(42)); - let _ = f(); +fn err_immediate_return() -> Result { + func_returning_result()?; + Ok(1) } + +fn err_immediate_return_and_do_something() -> Result { + func_returning_result()?; + do_something(); + Ok(1) +} + +// No warning +fn no_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + do_something(); + return Err(err); + } + Ok(1) +} + +// No warning +fn mixed_result_and_option() -> Option { + if let Err(err) = func_returning_result() { + return Some(err); + } + None +} + +// No warning +fn else_if_check() -> Result { + if true { + Ok(1) + } else if let Err(e) = func_returning_result() { + Err(e) + } else { + Err(-1) + } +} + +// No warning +#[allow(clippy::manual_map)] +#[rustfmt::skip] +fn option_map() -> Option { + if let Some(a) = Some(false) { + Some(!a) + } else { + None + } +} + +fn main() {} diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 60590fd93118..cdbc7b1606f8 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -1,5 +1,6 @@ // run-rustfix #![allow(unreachable_code)] +#![allow(dead_code)] #![allow(clippy::unnecessary_wraps)] fn some_func(a: Option) -> Option { @@ -186,26 +187,60 @@ fn f() -> NotOption { NotOption::First } -fn main() { - some_func(Some(42)); - some_func(None); - some_other_func(Some(42)); +fn do_something() {} - let copy_struct = CopyStruct { opt: Some(54) }; - copy_struct.func(); - - let move_struct = MoveStruct { - opt: Some(vec![42, 1337]), - }; - move_struct.ref_func(); - move_struct.clone().mov_func_reuse(); - move_struct.mov_func_no_use(); - - let so = SeemsOption::Some(45); - returns_something_similar_to_option(so); - - func(); - - let _ = result_func(Ok(42)); - let _ = f(); +fn err_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + return Err(err); + } + Ok(1) } + +fn err_immediate_return_and_do_something() -> Result { + if let Err(err) = func_returning_result() { + return Err(err); + } + do_something(); + Ok(1) +} + +// No warning +fn no_immediate_return() -> Result { + if let Err(err) = func_returning_result() { + do_something(); + return Err(err); + } + Ok(1) +} + +// No warning +fn mixed_result_and_option() -> Option { + if let Err(err) = func_returning_result() { + return Some(err); + } + None +} + +// No warning +fn else_if_check() -> Result { + if true { + Ok(1) + } else if let Err(e) = func_returning_result() { + Err(e) + } else { + Err(-1) + } +} + +// No warning +#[allow(clippy::manual_map)] +#[rustfmt::skip] +fn option_map() -> Option { + if let Some(a) = Some(false) { + Some(!a) + } else { + None + } +} + +fn main() {} diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 8d782b71dd6a..1b6cd524b2f2 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -1,5 +1,5 @@ error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:6:5 + --> $DIR/question_mark.rs:7:5 | LL | / if a.is_none() { LL | | return None; @@ -9,7 +9,7 @@ LL | | } = note: `-D clippy::question-mark` implied by `-D warnings` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:51:9 + --> $DIR/question_mark.rs:52:9 | LL | / if (self.opt).is_none() { LL | | return None; @@ -17,7 +17,7 @@ LL | | } | |_________^ help: replace it with: `(self.opt)?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:55:9 + --> $DIR/question_mark.rs:56:9 | LL | / if self.opt.is_none() { LL | | return None @@ -25,7 +25,7 @@ LL | | } | |_________^ help: replace it with: `self.opt?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:59:17 + --> $DIR/question_mark.rs:60:17 | LL | let _ = if self.opt.is_none() { | _________________^ @@ -35,8 +35,8 @@ LL | | self.opt LL | | }; | |_________^ help: replace it with: `Some(self.opt?)` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:65:17 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:66:17 | LL | let _ = if let Some(x) = self.opt { | _________________^ @@ -47,7 +47,7 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:82:9 + --> $DIR/question_mark.rs:83:9 | LL | / if self.opt.is_none() { LL | | return None; @@ -55,7 +55,7 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:90:9 + --> $DIR/question_mark.rs:91:9 | LL | / if self.opt.is_none() { LL | | return None; @@ -63,15 +63,15 @@ LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:98:9 + --> $DIR/question_mark.rs:99:9 | LL | / if self.opt.is_none() { LL | | return None; LL | | } | |_________^ help: replace it with: `self.opt.as_ref()?;` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:105:26 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:106:26 | LL | let v: &Vec<_> = if let Some(ref v) = self.opt { | __________________________^ @@ -81,8 +81,8 @@ LL | | return None; LL | | }; | |_________^ help: replace it with: `self.opt.as_ref()?` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:115:17 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:116:17 | LL | let v = if let Some(v) = self.opt { | _________________^ @@ -93,26 +93,42 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:130:5 + --> $DIR/question_mark.rs:131:5 | LL | / if f().is_none() { LL | | return None; LL | | } | |_____^ help: replace it with: `f()?;` -error: this if-let-else may be rewritten with the `?` operator - --> $DIR/question_mark.rs:142:13 +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:143:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> $DIR/question_mark.rs:144:5 + --> $DIR/question_mark.rs:145:5 | LL | / if x.is_err() { LL | | return x; LL | | } | |_____^ help: replace it with: `x?;` -error: aborting due to 13 previous errors +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:193:5 + | +LL | / if let Err(err) = func_returning_result() { +LL | | return Err(err); +LL | | } + | |_____^ help: replace it with: `func_returning_result()?;` + +error: this block may be rewritten with the `?` operator + --> $DIR/question_mark.rs:200:5 + | +LL | / if let Err(err) = func_returning_result() { +LL | | return Err(err); +LL | | } + | |_____^ help: replace it with: `func_returning_result()?;` + +error: aborting due to 15 previous errors diff --git a/tests/ui/same_name_method.rs b/tests/ui/same_name_method.rs index 9562b47f0c4f..daef95a425c9 100644 --- a/tests/ui/same_name_method.rs +++ b/tests/ui/same_name_method.rs @@ -62,7 +62,7 @@ mod should_lint { impl T1 for S {} } - mod mulitply_conflicit_trait { + mod multiply_conflicit_trait { use crate::{T1, T2}; struct S; diff --git a/tests/ui/search_is_some_fixable_some.fixed b/tests/ui/search_is_some_fixable_some.fixed index 5a2aee465d1b..385a9986aba0 100644 --- a/tests/ui/search_is_some_fixable_some.fixed +++ b/tests/ui/search_is_some_fixable_some.fixed @@ -216,3 +216,33 @@ mod issue7392 { let _ = v.iter().any(|fp| test_u32_2(*fp.field)); } } + +mod issue9120 { + fn make_arg_no_deref_impl() -> impl Fn(&&u32) -> bool { + move |x: &&u32| **x == 78 + } + + fn make_arg_no_deref_dyn() -> Box bool> { + Box::new(move |x: &&u32| **x == 78) + } + + fn wrapper bool>(v: Vec, func: T) -> bool { + #[allow(clippy::redundant_closure)] + v.iter().any(|x: &u32| func(&x)) + } + + fn do_tests() { + let v = vec![3, 2, 1, 0]; + let arg_no_deref_impl = make_arg_no_deref_impl(); + let arg_no_deref_dyn = make_arg_no_deref_dyn(); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().any(|x: &u32| arg_no_deref_impl(&x)); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().any(|x: &u32| arg_no_deref_dyn(&x)); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().any(|x: &u32| (*arg_no_deref_dyn)(&x)); + } +} diff --git a/tests/ui/search_is_some_fixable_some.rs b/tests/ui/search_is_some_fixable_some.rs index 0e98ae18a217..67e190ee3783 100644 --- a/tests/ui/search_is_some_fixable_some.rs +++ b/tests/ui/search_is_some_fixable_some.rs @@ -219,3 +219,33 @@ mod issue7392 { let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some(); } } + +mod issue9120 { + fn make_arg_no_deref_impl() -> impl Fn(&&u32) -> bool { + move |x: &&u32| **x == 78 + } + + fn make_arg_no_deref_dyn() -> Box bool> { + Box::new(move |x: &&u32| **x == 78) + } + + fn wrapper bool>(v: Vec, func: T) -> bool { + #[allow(clippy::redundant_closure)] + v.iter().find(|x: &&u32| func(x)).is_some() + } + + fn do_tests() { + let v = vec![3, 2, 1, 0]; + let arg_no_deref_impl = make_arg_no_deref_impl(); + let arg_no_deref_dyn = make_arg_no_deref_dyn(); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().find(|x: &&u32| arg_no_deref_impl(x)).is_some(); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().find(|x: &&u32| arg_no_deref_dyn(x)).is_some(); + + #[allow(clippy::redundant_closure)] + let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some(); + } +} diff --git a/tests/ui/search_is_some_fixable_some.stderr b/tests/ui/search_is_some_fixable_some.stderr index 8b424f18ef5b..c5c3c92c9182 100644 --- a/tests/ui/search_is_some_fixable_some.stderr +++ b/tests/ui/search_is_some_fixable_some.stderr @@ -264,5 +264,29 @@ error: called `is_some()` after searching an `Iterator` with `find` LL | let _ = v.iter().find(|fp| test_u32_2(*fp.field)).is_some(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|fp| test_u32_2(*fp.field))` -error: aborting due to 43 previous errors +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable_some.rs:234:18 + | +LL | v.iter().find(|x: &&u32| func(x)).is_some() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| func(&x))` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable_some.rs:243:26 + | +LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_impl(x)).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| arg_no_deref_impl(&x))` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable_some.rs:246:26 + | +LL | let _ = v.iter().find(|x: &&u32| arg_no_deref_dyn(x)).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| arg_no_deref_dyn(&x))` + +error: called `is_some()` after searching an `Iterator` with `find` + --> $DIR/search_is_some_fixable_some.rs:249:26 + | +LL | let _ = v.iter().find(|x: &&u32| (*arg_no_deref_dyn)(x)).is_some(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `any()` instead: `any(|x: &u32| (*arg_no_deref_dyn)(&x))` + +error: aborting due to 47 previous errors diff --git a/tests/ui/shadow.stderr b/tests/ui/shadow.stderr index 3bd41d062604..43d76094d0e8 100644 --- a/tests/ui/shadow.stderr +++ b/tests/ui/shadow.stderr @@ -265,5 +265,17 @@ note: previous binding is here LL | pub async fn foo2(_a: i32, _b: i64) { | ^^ -error: aborting due to 22 previous errors +error: `x` shadows a previous, unrelated binding + --> $DIR/shadow.rs:94:21 + | +LL | if let Some(x) = Some(1) { x } else { 1 } + | ^ + | +note: previous binding is here + --> $DIR/shadow.rs:93:13 + | +LL | let x = 1; + | ^ + +error: aborting due to 23 previous errors diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 185e5009b60f..84ecf1ea53ed 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -620,4 +620,11 @@ fn should_trigger_lint_without_significant_drop_in_arm() { }; } +fn should_not_trigger_on_significant_iterator_drop() { + let lines = std::io::stdin().lines(); + for line in lines { + println!("foo: {}", line.unwrap()); + } +} + fn main() {} diff --git a/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs new file mode 100644 index 000000000000..74f05ec1f658 --- /dev/null +++ b/tests/ui/std_instead_of_core.rs @@ -0,0 +1,39 @@ +#![warn(clippy::std_instead_of_core)] +#![allow(unused_imports)] + +extern crate alloc; + +#[warn(clippy::std_instead_of_core)] +fn std_instead_of_core() { + // Regular import + use std::hash::Hasher; + // Absolute path + use ::std::hash::Hash; + + // Multiple imports + use std::fmt::{Debug, Result}; + + // Function calls + let ptr = std::ptr::null::(); + let ptr_mut = ::std::ptr::null_mut::(); + + // Types + let cell = std::cell::Cell::new(8u32); + let cell_absolute = ::std::cell::Cell::new(8u32); +} + +#[warn(clippy::std_instead_of_alloc)] +fn std_instead_of_alloc() { + use std::vec::Vec; +} + +#[warn(clippy::alloc_instead_of_core)] +fn alloc_instead_of_core() { + use alloc::slice::from_ref; +} + +fn main() { + std_instead_of_core(); + std_instead_of_alloc(); + alloc_instead_of_core(); +} diff --git a/tests/ui/std_instead_of_core.stderr b/tests/ui/std_instead_of_core.stderr new file mode 100644 index 000000000000..9f1644835c10 --- /dev/null +++ b/tests/ui/std_instead_of_core.stderr @@ -0,0 +1,85 @@ +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:9:9 + | +LL | use std::hash::Hasher; + | ^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::std-instead-of-core` implied by `-D warnings` + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:11:9 + | +LL | use ::std::hash::Hash; + | ^^^^^^^^^^^^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:14:20 + | +LL | use std::fmt::{Debug, Result}; + | ^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:14:27 + | +LL | use std::fmt::{Debug, Result}; + | ^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:17:15 + | +LL | let ptr = std::ptr::null::(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:18:19 + | +LL | let ptr_mut = ::std::ptr::null_mut::(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:21:16 + | +LL | let cell = std::cell::Cell::new(8u32); + | ^^^^^^^^^^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `core` + --> $DIR/std_instead_of_core.rs:22:25 + | +LL | let cell_absolute = ::std::cell::Cell::new(8u32); + | ^^^^^^^^^^^^^^^^^ + | + = help: consider importing the item from `core` + +error: used import from `std` instead of `alloc` + --> $DIR/std_instead_of_core.rs:27:9 + | +LL | use std::vec::Vec; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::std-instead-of-alloc` implied by `-D warnings` + = help: consider importing the item from `alloc` + +error: used import from `alloc` instead of `core` + --> $DIR/std_instead_of_core.rs:32:9 + | +LL | use alloc::slice::from_ref; + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::alloc-instead-of-core` implied by `-D warnings` + = help: consider importing the item from `core` + +error: aborting due to 10 previous errors + diff --git a/tests/ui/trait_duplication_in_bounds.rs b/tests/ui/trait_duplication_in_bounds.rs index a21d4c5d637d..a5751c58aab8 100644 --- a/tests/ui/trait_duplication_in_bounds.rs +++ b/tests/ui/trait_duplication_in_bounds.rs @@ -1,4 +1,5 @@ #![deny(clippy::trait_duplication_in_bounds)] +#![allow(unused)] use std::collections::BTreeMap; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; @@ -98,4 +99,114 @@ trait FooIter: Iterator { // This should not lint fn impl_trait(_: impl AsRef, _: impl AsRef) {} +mod repeated_where_clauses_or_trait_bounds { + fn bad_foo(arg0: T, argo1: U) { + unimplemented!(); + } + + fn bad_bar(arg0: T, arg1: U) + where + T: Clone + Clone + Clone + Copy, + U: Clone + Copy, + { + unimplemented!(); + } + + fn good_bar(arg0: T, arg1: U) { + unimplemented!(); + } + + fn good_foo(arg0: T, arg1: U) + where + T: Clone + Copy, + U: Clone + Copy, + { + unimplemented!(); + } + + trait GoodSelfTraitBound: Clone + Copy { + fn f(); + } + + trait GoodSelfWhereClause { + fn f() + where + Self: Clone + Copy; + } + + trait BadSelfTraitBound: Clone + Clone + Clone { + fn f(); + } + + trait BadSelfWhereClause { + fn f() + where + Self: Clone + Clone + Clone; + } + + trait GoodTraitBound { + fn f(); + } + + trait GoodWhereClause { + fn f() + where + T: Clone + Copy, + U: Clone + Copy; + } + + trait BadTraitBound { + fn f(); + } + + trait BadWhereClause { + fn f() + where + T: Clone + Clone + Clone + Copy, + U: Clone + Copy; + } + + struct GoodStructBound { + t: T, + u: U, + } + + impl GoodTraitBound for GoodStructBound { + // this should not warn + fn f() {} + } + + struct GoodStructWhereClause; + + impl GoodTraitBound for GoodStructWhereClause + where + T: Clone + Copy, + U: Clone + Copy, + { + // this should not warn + fn f() {} + } + + fn no_error_separate_arg_bounds(program: impl AsRef<()>, dir: impl AsRef<()>, args: &[impl AsRef<()>]) {} + + trait GenericTrait {} + + // This should not warn but currently does see #8757 + fn good_generic + GenericTrait>(arg0: T) { + unimplemented!(); + } + + fn bad_generic + GenericTrait + GenericTrait>(arg0: T) { + unimplemented!(); + } + + mod foo { + pub trait Clone {} + } + + fn qualified_path(arg0: T) { + unimplemented!(); + } +} + fn main() {} diff --git a/tests/ui/trait_duplication_in_bounds.stderr b/tests/ui/trait_duplication_in_bounds.stderr index 6f8c8e47dfbf..7ef04e52708f 100644 --- a/tests/ui/trait_duplication_in_bounds.stderr +++ b/tests/ui/trait_duplication_in_bounds.stderr @@ -1,5 +1,5 @@ error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:6:15 + --> $DIR/trait_duplication_in_bounds.rs:7:15 | LL | fn bad_foo(arg0: T, arg1: Z) | ^^^^^ @@ -12,7 +12,7 @@ LL | #![deny(clippy::trait_duplication_in_bounds)] = help: consider removing this trait bound error: this trait bound is already specified in the where clause - --> $DIR/trait_duplication_in_bounds.rs:6:23 + --> $DIR/trait_duplication_in_bounds.rs:7:23 | LL | fn bad_foo(arg0: T, arg1: Z) | ^^^^^^^ @@ -20,7 +20,7 @@ LL | fn bad_foo(arg0: T, arg1: Z) = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:35:15 + --> $DIR/trait_duplication_in_bounds.rs:36:15 | LL | Self: Default; | ^^^^^^^ @@ -28,7 +28,7 @@ LL | Self: Default; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:49:15 + --> $DIR/trait_duplication_in_bounds.rs:50:15 | LL | Self: Default + Clone; | ^^^^^^^ @@ -36,7 +36,7 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:55:15 + --> $DIR/trait_duplication_in_bounds.rs:56:15 | LL | Self: Default + Clone; | ^^^^^^^ @@ -44,7 +44,7 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:55:25 + --> $DIR/trait_duplication_in_bounds.rs:56:25 | LL | Self: Default + Clone; | ^^^^^ @@ -52,7 +52,7 @@ LL | Self: Default + Clone; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:58:15 + --> $DIR/trait_duplication_in_bounds.rs:59:15 | LL | Self: Default; | ^^^^^^^ @@ -60,12 +60,108 @@ LL | Self: Default; = help: consider removing this trait bound error: this trait bound is already specified in trait declaration - --> $DIR/trait_duplication_in_bounds.rs:93:15 + --> $DIR/trait_duplication_in_bounds.rs:94:15 | LL | Self: Iterator, | ^^^^^^^^^^^^^^^^^^^^ | = help: consider removing this trait bound -error: aborting due to 8 previous errors +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:103:19 + | +LL | fn bad_foo(arg0: T, argo1: U) { + | ^^^^^ + | + = help: consider removing this trait bound + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:103:19 + | +LL | fn bad_foo(arg0: T, argo1: U) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:109:12 + | +LL | T: Clone + Clone + Clone + Copy, + | ^^^^^ + | + = help: consider removing this trait bound + +error: these where clauses contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:109:12 + | +LL | T: Clone + Clone + Clone + Copy, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:137:30 + | +LL | trait BadSelfTraitBound: Clone + Clone + Clone { + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` + +error: these where clauses contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:144:19 + | +LL | Self: Clone + Clone + Clone; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone` + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:158:28 + | +LL | trait BadTraitBound { + | ^^^^^ + | + = help: consider removing this trait bound + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:158:28 + | +LL | trait BadTraitBound { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: these where clauses contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:165:16 + | +LL | T: Clone + Clone + Clone + Copy, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + Copy` + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:195:24 + | +LL | fn good_generic + GenericTrait>(arg0: T) { + | ^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:199:23 + | +LL | fn bad_generic + GenericTrait + GenericTrait>(arg0: T) { + | ^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:199:23 + | +LL | fn bad_generic + GenericTrait + GenericTrait>(arg0: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `GenericTrait + GenericTrait` + +error: this trait bound is already specified in the where clause + --> $DIR/trait_duplication_in_bounds.rs:207:26 + | +LL | fn qualified_path(arg0: T) { + | ^^^^^^^^^^^^^^^^^ + | + = help: consider removing this trait bound + +error: these bounds contain repeated elements + --> $DIR/trait_duplication_in_bounds.rs:207:26 + | +LL | fn qualified_path(arg0: T) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Clone + foo::Clone` + +error: aborting due to 22 previous errors diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.fixed b/tests/ui/transmutes_expressible_as_ptr_casts.fixed index b425cdd6cbfd..539239fc18f9 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.fixed +++ b/tests/ui/transmutes_expressible_as_ptr_casts.fixed @@ -1,6 +1,6 @@ // run-rustfix #![warn(clippy::transmutes_expressible_as_ptr_casts)] -// These two warnings currrently cover the cases transmutes_expressible_as_ptr_casts +// These two warnings currently cover the cases transmutes_expressible_as_ptr_casts // would otherwise be responsible for #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] diff --git a/tests/ui/transmutes_expressible_as_ptr_casts.rs b/tests/ui/transmutes_expressible_as_ptr_casts.rs index 8fd57c596527..b9e446dc89a9 100644 --- a/tests/ui/transmutes_expressible_as_ptr_casts.rs +++ b/tests/ui/transmutes_expressible_as_ptr_casts.rs @@ -1,6 +1,6 @@ // run-rustfix #![warn(clippy::transmutes_expressible_as_ptr_casts)] -// These two warnings currrently cover the cases transmutes_expressible_as_ptr_casts +// These two warnings currently cover the cases transmutes_expressible_as_ptr_casts // would otherwise be responsible for #![warn(clippy::useless_transmute)] #![warn(clippy::transmute_ptr_to_ptr)] diff --git a/tests/ui/type_repetition_in_bounds.rs b/tests/ui/type_repetition_in_bounds.rs index d11432f90461..2eca1f4701c9 100644 --- a/tests/ui/type_repetition_in_bounds.rs +++ b/tests/ui/type_repetition_in_bounds.rs @@ -79,6 +79,18 @@ where u: U, } +// Check for the `?` in `?Sized` +pub fn f() +where + T: Clone, +{ +} +pub fn g() +where + T: ?Sized, +{ +} + // This should not lint fn impl_trait(_: impl AsRef, _: impl AsRef) {} diff --git a/tests/ui/type_repetition_in_bounds.stderr b/tests/ui/type_repetition_in_bounds.stderr index 148c19c7d070..1d88714814d4 100644 --- a/tests/ui/type_repetition_in_bounds.stderr +++ b/tests/ui/type_repetition_in_bounds.stderr @@ -19,5 +19,21 @@ LL | Self: Copy + Default + Ord, | = help: consider combining the bounds: `Self: Clone + Copy + Default + Ord` -error: aborting due to 2 previous errors +error: this type has already been used as a bound predicate + --> $DIR/type_repetition_in_bounds.rs:85:5 + | +LL | T: Clone, + | ^^^^^^^^ + | + = help: consider combining the bounds: `T: ?Sized + Clone` + +error: this type has already been used as a bound predicate + --> $DIR/type_repetition_in_bounds.rs:90:5 + | +LL | T: ?Sized, + | ^^^^^^^^^ + | + = help: consider combining the bounds: `T: Clone + ?Sized` + +error: aborting due to 4 previous errors diff --git a/tests/ui/undocumented_unsafe_blocks.rs b/tests/ui/undocumented_unsafe_blocks.rs index 33b6a82f9d2c..08aee4332151 100644 --- a/tests/ui/undocumented_unsafe_blocks.rs +++ b/tests/ui/undocumented_unsafe_blocks.rs @@ -250,6 +250,11 @@ fn from_proc_macro() { proc_macro_unsafe::unsafe_block!(token); } +fn in_closure(x: *const u32) { + // Safety: reason + let _ = || unsafe { *x }; +} + // Invalid comments #[rustfmt::skip] @@ -351,9 +356,9 @@ mod unsafe_impl_smoke_test { #[rustfmt::skip] mod sub_mod2 { - // + // // SAFETY: ok - // + // unsafe impl B for (u32) {} unsafe trait B {} diff --git a/tests/ui/undocumented_unsafe_blocks.stderr b/tests/ui/undocumented_unsafe_blocks.stderr index b79949e9d06d..c6a2127443be 100644 --- a/tests/ui/undocumented_unsafe_blocks.stderr +++ b/tests/ui/undocumented_unsafe_blocks.stderr @@ -1,5 +1,5 @@ error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:257:19 + --> $DIR/undocumented_unsafe_blocks.rs:262:19 | LL | /* Safety: */ unsafe {} | ^^^^^^^^^ @@ -8,7 +8,7 @@ LL | /* Safety: */ unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:261:5 + --> $DIR/undocumented_unsafe_blocks.rs:266:5 | LL | unsafe {} | ^^^^^^^^^ @@ -16,7 +16,7 @@ LL | unsafe {} = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:265:14 + --> $DIR/undocumented_unsafe_blocks.rs:270:14 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -24,7 +24,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:265:29 + --> $DIR/undocumented_unsafe_blocks.rs:270:29 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -32,7 +32,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:265:48 + --> $DIR/undocumented_unsafe_blocks.rs:270:48 | LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; | ^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:269:18 + --> $DIR/undocumented_unsafe_blocks.rs:274:18 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -48,7 +48,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:269:37 + --> $DIR/undocumented_unsafe_blocks.rs:274:37 | LL | let _ = (42, unsafe {}, "test", unsafe {}); | ^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:273:14 + --> $DIR/undocumented_unsafe_blocks.rs:278:14 | LL | let _ = *unsafe { &42 }; | ^^^^^^^^^^^^^^ @@ -64,7 +64,7 @@ LL | let _ = *unsafe { &42 }; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:278:19 + --> $DIR/undocumented_unsafe_blocks.rs:283:19 | LL | let _ = match unsafe {} { | ^^^^^^^^^ @@ -72,7 +72,7 @@ LL | let _ = match unsafe {} { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:284:14 + --> $DIR/undocumented_unsafe_blocks.rs:289:14 | LL | let _ = &unsafe {}; | ^^^^^^^^^ @@ -80,7 +80,7 @@ LL | let _ = &unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:288:14 + --> $DIR/undocumented_unsafe_blocks.rs:293:14 | LL | let _ = [unsafe {}; 5]; | ^^^^^^^^^ @@ -88,7 +88,7 @@ LL | let _ = [unsafe {}; 5]; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:292:13 + --> $DIR/undocumented_unsafe_blocks.rs:297:13 | LL | let _ = unsafe {}; | ^^^^^^^^^ @@ -96,7 +96,7 @@ LL | let _ = unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:302:8 + --> $DIR/undocumented_unsafe_blocks.rs:307:8 | LL | t!(unsafe {}); | ^^^^^^^^^ @@ -104,7 +104,7 @@ LL | t!(unsafe {}); = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:308:13 + --> $DIR/undocumented_unsafe_blocks.rs:313:13 | LL | unsafe {} | ^^^^^^^^^ @@ -116,7 +116,7 @@ LL | t!(); = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:316:5 + --> $DIR/undocumented_unsafe_blocks.rs:321:5 | LL | unsafe {} // SAFETY: | ^^^^^^^^^ @@ -124,7 +124,7 @@ LL | unsafe {} // SAFETY: = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:320:5 + --> $DIR/undocumented_unsafe_blocks.rs:325:5 | LL | unsafe { | ^^^^^^^^ @@ -132,7 +132,7 @@ LL | unsafe { = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:330:5 + --> $DIR/undocumented_unsafe_blocks.rs:335:5 | LL | unsafe {}; | ^^^^^^^^^ @@ -140,7 +140,7 @@ LL | unsafe {}; = help: consider adding a safety comment on the preceding line error: unsafe block missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:334:20 + --> $DIR/undocumented_unsafe_blocks.rs:339:20 | LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -148,7 +148,7 @@ LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) }); = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:341:5 + --> $DIR/undocumented_unsafe_blocks.rs:346:5 | LL | unsafe impl A for () {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -156,7 +156,7 @@ LL | unsafe impl A for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:348:9 + --> $DIR/undocumented_unsafe_blocks.rs:353:9 | LL | unsafe impl B for (u32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -164,7 +164,7 @@ LL | unsafe impl B for (u32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:369:13 + --> $DIR/undocumented_unsafe_blocks.rs:374:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -176,7 +176,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:394:13 + --> $DIR/undocumented_unsafe_blocks.rs:399:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -188,7 +188,7 @@ LL | no_safety_comment!(()); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:402:5 + --> $DIR/undocumented_unsafe_blocks.rs:407:5 | LL | unsafe impl T for (i32) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -196,7 +196,7 @@ LL | unsafe impl T for (i32) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:394:13 + --> $DIR/undocumented_unsafe_blocks.rs:399:13 | LL | unsafe impl T for $t {} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -208,7 +208,7 @@ LL | no_safety_comment!(u32); = note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info) error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:408:5 + --> $DIR/undocumented_unsafe_blocks.rs:413:5 | LL | unsafe impl T for (bool) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -216,7 +216,7 @@ LL | unsafe impl T for (bool) {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:454:5 + --> $DIR/undocumented_unsafe_blocks.rs:459:5 | LL | unsafe impl NoComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -224,7 +224,7 @@ LL | unsafe impl NoComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:458:19 + --> $DIR/undocumented_unsafe_blocks.rs:463:19 | LL | /* SAFETY: */ unsafe impl InlineComment for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -232,7 +232,7 @@ LL | /* SAFETY: */ unsafe impl InlineComment for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:462:5 + --> $DIR/undocumented_unsafe_blocks.rs:467:5 | LL | unsafe impl TrailingComment for () {} // SAFETY: | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -240,7 +240,7 @@ LL | unsafe impl TrailingComment for () {} // SAFETY: = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:467:5 + --> $DIR/undocumented_unsafe_blocks.rs:472:5 | LL | unsafe impl Interference for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -248,7 +248,7 @@ LL | unsafe impl Interference for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:474:5 + --> $DIR/undocumented_unsafe_blocks.rs:479:5 | LL | unsafe impl ImplInFn for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -256,7 +256,7 @@ LL | unsafe impl ImplInFn for () {} = help: consider adding a safety comment on the preceding line error: unsafe impl missing a safety comment - --> $DIR/undocumented_unsafe_blocks.rs:483:1 + --> $DIR/undocumented_unsafe_blocks.rs:488:1 | LL | unsafe impl CrateRoot for () {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/unnecessary_lazy_eval.fixed b/tests/ui/unnecessary_lazy_eval.fixed index 65fcdc43061b..eed817968832 100644 --- a/tests/ui/unnecessary_lazy_eval.fixed +++ b/tests/ui/unnecessary_lazy_eval.fixed @@ -30,6 +30,7 @@ fn main() { let ext_opt = Some(42); let nested_opt = Some(Some(42)); let nested_tuple_opt = Some(Some((42, 43))); + let cond = true; // Should lint - Option let _ = opt.unwrap_or(2); @@ -42,6 +43,7 @@ fn main() { let _ = opt.get_or_insert(2); let _ = opt.ok_or(2); let _ = nested_tuple_opt.unwrap_or(Some((1, 2))); + let _ = cond.then_some(astronomers_pi); // Cases when unwrap is not called on a simple variable let _ = Some(10).unwrap_or(2); diff --git a/tests/ui/unnecessary_lazy_eval.rs b/tests/ui/unnecessary_lazy_eval.rs index 206080ed69ad..1588db79b38a 100644 --- a/tests/ui/unnecessary_lazy_eval.rs +++ b/tests/ui/unnecessary_lazy_eval.rs @@ -30,6 +30,7 @@ fn main() { let ext_opt = Some(42); let nested_opt = Some(Some(42)); let nested_tuple_opt = Some(Some((42, 43))); + let cond = true; // Should lint - Option let _ = opt.unwrap_or_else(|| 2); @@ -42,6 +43,7 @@ fn main() { let _ = opt.get_or_insert_with(|| 2); let _ = opt.ok_or_else(|| 2); let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); + let _ = cond.then(|| astronomers_pi); // Cases when unwrap is not called on a simple variable let _ = Some(10).unwrap_or_else(|| 2); diff --git a/tests/ui/unnecessary_lazy_eval.stderr b/tests/ui/unnecessary_lazy_eval.stderr index 7e4dd7730e71..83dc7fd832c3 100644 --- a/tests/ui/unnecessary_lazy_eval.stderr +++ b/tests/ui/unnecessary_lazy_eval.stderr @@ -1,5 +1,5 @@ error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:35:13 + --> $DIR/unnecessary_lazy_eval.rs:36:13 | LL | let _ = opt.unwrap_or_else(|| 2); | ^^^^-------------------- @@ -9,7 +9,7 @@ LL | let _ = opt.unwrap_or_else(|| 2); = note: `-D clippy::unnecessary-lazy-evaluations` implied by `-D warnings` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:36:13 + --> $DIR/unnecessary_lazy_eval.rs:37:13 | LL | let _ = opt.unwrap_or_else(|| astronomers_pi); | ^^^^--------------------------------- @@ -17,7 +17,7 @@ LL | let _ = opt.unwrap_or_else(|| astronomers_pi); | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:37:13 + --> $DIR/unnecessary_lazy_eval.rs:38:13 | LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); | ^^^^------------------------------------- @@ -25,7 +25,7 @@ LL | let _ = opt.unwrap_or_else(|| ext_str.some_field); | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:39:13 + --> $DIR/unnecessary_lazy_eval.rs:40:13 | LL | let _ = opt.and_then(|_| ext_opt); | ^^^^--------------------- @@ -33,7 +33,7 @@ LL | let _ = opt.and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:40:13 + --> $DIR/unnecessary_lazy_eval.rs:41:13 | LL | let _ = opt.or_else(|| ext_opt); | ^^^^------------------- @@ -41,7 +41,7 @@ LL | let _ = opt.or_else(|| ext_opt); | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:41:13 + --> $DIR/unnecessary_lazy_eval.rs:42:13 | LL | let _ = opt.or_else(|| None); | ^^^^---------------- @@ -49,7 +49,7 @@ LL | let _ = opt.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:42:13 + --> $DIR/unnecessary_lazy_eval.rs:43:13 | LL | let _ = opt.get_or_insert_with(|| 2); | ^^^^------------------------ @@ -57,7 +57,7 @@ LL | let _ = opt.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:43:13 + --> $DIR/unnecessary_lazy_eval.rs:44:13 | LL | let _ = opt.ok_or_else(|| 2); | ^^^^---------------- @@ -65,15 +65,23 @@ LL | let _ = opt.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:44:13 + --> $DIR/unnecessary_lazy_eval.rs:45:13 | LL | let _ = nested_tuple_opt.unwrap_or_else(|| Some((1, 2))); | ^^^^^^^^^^^^^^^^^------------------------------- | | | help: use `unwrap_or(..)` instead: `unwrap_or(Some((1, 2)))` +error: unnecessary closure used with `bool::then` + --> $DIR/unnecessary_lazy_eval.rs:46:13 + | +LL | let _ = cond.then(|| astronomers_pi); + | ^^^^^----------------------- + | | + | help: use `then_some(..)` instead: `then_some(astronomers_pi)` + error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:47:13 + --> $DIR/unnecessary_lazy_eval.rs:49:13 | LL | let _ = Some(10).unwrap_or_else(|| 2); | ^^^^^^^^^-------------------- @@ -81,7 +89,7 @@ LL | let _ = Some(10).unwrap_or_else(|| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:48:13 + --> $DIR/unnecessary_lazy_eval.rs:50:13 | LL | let _ = Some(10).and_then(|_| ext_opt); | ^^^^^^^^^--------------------- @@ -89,7 +97,7 @@ LL | let _ = Some(10).and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:49:28 + --> $DIR/unnecessary_lazy_eval.rs:51:28 | LL | let _: Option = None.or_else(|| ext_opt); | ^^^^^------------------- @@ -97,7 +105,7 @@ LL | let _: Option = None.or_else(|| ext_opt); | help: use `or(..)` instead: `or(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:50:13 + --> $DIR/unnecessary_lazy_eval.rs:52:13 | LL | let _ = None.get_or_insert_with(|| 2); | ^^^^^------------------------ @@ -105,7 +113,7 @@ LL | let _ = None.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:51:35 + --> $DIR/unnecessary_lazy_eval.rs:53:35 | LL | let _: Result = None.ok_or_else(|| 2); | ^^^^^---------------- @@ -113,7 +121,7 @@ LL | let _: Result = None.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:52:28 + --> $DIR/unnecessary_lazy_eval.rs:54:28 | LL | let _: Option = None.or_else(|| None); | ^^^^^---------------- @@ -121,7 +129,7 @@ LL | let _: Option = None.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:55:13 + --> $DIR/unnecessary_lazy_eval.rs:57:13 | LL | let _ = deep.0.unwrap_or_else(|| 2); | ^^^^^^^-------------------- @@ -129,7 +137,7 @@ LL | let _ = deep.0.unwrap_or_else(|| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:56:13 + --> $DIR/unnecessary_lazy_eval.rs:58:13 | LL | let _ = deep.0.and_then(|_| ext_opt); | ^^^^^^^--------------------- @@ -137,7 +145,7 @@ LL | let _ = deep.0.and_then(|_| ext_opt); | help: use `and(..)` instead: `and(ext_opt)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:57:13 + --> $DIR/unnecessary_lazy_eval.rs:59:13 | LL | let _ = deep.0.or_else(|| None); | ^^^^^^^---------------- @@ -145,7 +153,7 @@ LL | let _ = deep.0.or_else(|| None); | help: use `or(..)` instead: `or(None)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:58:13 + --> $DIR/unnecessary_lazy_eval.rs:60:13 | LL | let _ = deep.0.get_or_insert_with(|| 2); | ^^^^^^^------------------------ @@ -153,7 +161,7 @@ LL | let _ = deep.0.get_or_insert_with(|| 2); | help: use `get_or_insert(..)` instead: `get_or_insert(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:59:13 + --> $DIR/unnecessary_lazy_eval.rs:61:13 | LL | let _ = deep.0.ok_or_else(|| 2); | ^^^^^^^---------------- @@ -161,7 +169,7 @@ LL | let _ = deep.0.ok_or_else(|| 2); | help: use `ok_or(..)` instead: `ok_or(2)` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:79:28 + --> $DIR/unnecessary_lazy_eval.rs:81:28 | LL | let _: Option = None.or_else(|| Some(3)); | ^^^^^------------------- @@ -169,7 +177,7 @@ LL | let _: Option = None.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:80:13 + --> $DIR/unnecessary_lazy_eval.rs:82:13 | LL | let _ = deep.0.or_else(|| Some(3)); | ^^^^^^^------------------- @@ -177,7 +185,7 @@ LL | let _ = deep.0.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Option::None` - --> $DIR/unnecessary_lazy_eval.rs:81:13 + --> $DIR/unnecessary_lazy_eval.rs:83:13 | LL | let _ = opt.or_else(|| Some(3)); | ^^^^------------------- @@ -185,7 +193,7 @@ LL | let _ = opt.or_else(|| Some(3)); | help: use `or(..)` instead: `or(Some(3))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:87:13 + --> $DIR/unnecessary_lazy_eval.rs:89:13 | LL | let _ = res2.unwrap_or_else(|_| 2); | ^^^^^--------------------- @@ -193,7 +201,7 @@ LL | let _ = res2.unwrap_or_else(|_| 2); | help: use `unwrap_or(..)` instead: `unwrap_or(2)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:88:13 + --> $DIR/unnecessary_lazy_eval.rs:90:13 | LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); | ^^^^^---------------------------------- @@ -201,7 +209,7 @@ LL | let _ = res2.unwrap_or_else(|_| astronomers_pi); | help: use `unwrap_or(..)` instead: `unwrap_or(astronomers_pi)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:89:13 + --> $DIR/unnecessary_lazy_eval.rs:91:13 | LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); | ^^^^^-------------------------------------- @@ -209,7 +217,7 @@ LL | let _ = res2.unwrap_or_else(|_| ext_str.some_field); | help: use `unwrap_or(..)` instead: `unwrap_or(ext_str.some_field)` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:111:35 + --> $DIR/unnecessary_lazy_eval.rs:113:35 | LL | let _: Result = res.and_then(|_| Err(2)); | ^^^^-------------------- @@ -217,7 +225,7 @@ LL | let _: Result = res.and_then(|_| Err(2)); | help: use `and(..)` instead: `and(Err(2))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:112:35 + --> $DIR/unnecessary_lazy_eval.rs:114:35 | LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); | ^^^^--------------------------------- @@ -225,7 +233,7 @@ LL | let _: Result = res.and_then(|_| Err(astronomers_pi)); | help: use `and(..)` instead: `and(Err(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:113:35 + --> $DIR/unnecessary_lazy_eval.rs:115:35 | LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)); | ^^^^------------------------------------- @@ -233,7 +241,7 @@ LL | let _: Result = res.and_then(|_| Err(ext_str.some_field)) | help: use `and(..)` instead: `and(Err(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:115:35 + --> $DIR/unnecessary_lazy_eval.rs:117:35 | LL | let _: Result = res.or_else(|_| Ok(2)); | ^^^^------------------ @@ -241,7 +249,7 @@ LL | let _: Result = res.or_else(|_| Ok(2)); | help: use `or(..)` instead: `or(Ok(2))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:116:35 + --> $DIR/unnecessary_lazy_eval.rs:118:35 | LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); | ^^^^------------------------------- @@ -249,7 +257,7 @@ LL | let _: Result = res.or_else(|_| Ok(astronomers_pi)); | help: use `or(..)` instead: `or(Ok(astronomers_pi))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:117:35 + --> $DIR/unnecessary_lazy_eval.rs:119:35 | LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); | ^^^^----------------------------------- @@ -257,7 +265,7 @@ LL | let _: Result = res.or_else(|_| Ok(ext_str.some_field)); | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` error: unnecessary closure used to substitute value for `Result::Err` - --> $DIR/unnecessary_lazy_eval.rs:118:35 + --> $DIR/unnecessary_lazy_eval.rs:120:35 | LL | let _: Result = res. | ___________________________________^ @@ -271,5 +279,5 @@ LL | | or_else(|_| Ok(ext_str.some_field)); | | | help: use `or(..)` instead: `or(Ok(ext_str.some_field))` -error: aborting due to 33 previous errors +error: aborting due to 34 previous errors diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index e9ff64431e19..c57c46736342 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -7,7 +7,8 @@ unused_mut, dead_code, clippy::equatable_if_let, - clippy::manual_find + clippy::manual_find, + clippy::redundant_closure_call )] fn base() { @@ -259,7 +260,7 @@ fn issue1924() { fn f(&mut self) -> Option { // Used as a field. for i in self.0.by_ref() { - if !(3..=7).contains(&i) { + if !(3..8).contains(&i) { return Some(i); } } @@ -403,6 +404,47 @@ fn issue_8113() { } } +fn fn_once_closure() { + let mut it = 0..10; + (|| { + for x in it { + if x % 2 == 0 { + break; + } + } + })(); + + fn f(_: impl FnOnce()) {} + let mut it = 0..10; + f(|| { + for x in it { + if x % 2 == 0 { + break; + } + } + }); + + fn f2(_: impl FnMut()) {} + let mut it = 0..10; + f2(|| { + for x in it.by_ref() { + if x % 2 == 0 { + break; + } + } + }); + + fn f3(_: fn()) {} + f3(|| { + let mut it = 0..10; + for x in it { + if x % 2 == 0 { + break; + } + } + }) +} + fn main() { let mut it = 0..20; for _ in it { diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index 03da39526b2f..8b9a2dbcce3a 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -7,7 +7,8 @@ unused_mut, dead_code, clippy::equatable_if_let, - clippy::manual_find + clippy::manual_find, + clippy::redundant_closure_call )] fn base() { @@ -259,7 +260,7 @@ fn issue1924() { fn f(&mut self) -> Option { // Used as a field. while let Some(i) = self.0.next() { - if i < 3 || i > 7 { + if !(3..8).contains(&i) { return Some(i); } } @@ -403,6 +404,47 @@ fn issue_8113() { } } +fn fn_once_closure() { + let mut it = 0..10; + (|| { + while let Some(x) = it.next() { + if x % 2 == 0 { + break; + } + } + })(); + + fn f(_: impl FnOnce()) {} + let mut it = 0..10; + f(|| { + while let Some(x) = it.next() { + if x % 2 == 0 { + break; + } + } + }); + + fn f2(_: impl FnMut()) {} + let mut it = 0..10; + f2(|| { + while let Some(x) = it.next() { + if x % 2 == 0 { + break; + } + } + }); + + fn f3(_: fn()) {} + f3(|| { + let mut it = 0..10; + while let Some(x) = it.next() { + if x % 2 == 0 { + break; + } + } + }) +} + fn main() { let mut it = 0..20; while let Some(..) = it.next() { diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index 42859243855a..3236765e1db0 100644 --- a/tests/ui/while_let_on_iterator.stderr +++ b/tests/ui/while_let_on_iterator.stderr @@ -1,5 +1,5 @@ error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:15:5 + --> $DIR/while_let_on_iterator.rs:16:5 | LL | while let Option::Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` @@ -7,138 +7,154 @@ LL | while let Option::Some(x) = iter.next() { = note: `-D clippy::while-let-on-iterator` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:20:5 + --> $DIR/while_let_on_iterator.rs:21:5 | LL | while let Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:25:5 + --> $DIR/while_let_on_iterator.rs:26:5 | LL | while let Some(_) = iter.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:101:9 + --> $DIR/while_let_on_iterator.rs:102:9 | LL | while let Some([..]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:108:9 + --> $DIR/while_let_on_iterator.rs:109:9 | LL | while let Some([_x]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:121:9 + --> $DIR/while_let_on_iterator.rs:122:9 | LL | while let Some(x @ [_]) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:141:9 + --> $DIR/while_let_on_iterator.rs:142:9 | LL | while let Some(_) = y.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:198:9 + --> $DIR/while_let_on_iterator.rs:199:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:209:5 + --> $DIR/while_let_on_iterator.rs:210:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:211:9 + --> $DIR/while_let_on_iterator.rs:212:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:220:9 + --> $DIR/while_let_on_iterator.rs:221:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:229:9 + --> $DIR/while_let_on_iterator.rs:230:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:246:9 + --> $DIR/while_let_on_iterator.rs:247:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:261:13 + --> $DIR/while_let_on_iterator.rs:262:13 | LL | while let Some(i) = self.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.by_ref()` -error: manual `!RangeInclusive::contains` implementation - --> $DIR/while_let_on_iterator.rs:262:20 - | -LL | if i < 3 || i > 7 { - | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` - | - = note: `-D clippy::manual-range-contains` implied by `-D warnings` - error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:293:13 + --> $DIR/while_let_on_iterator.rs:294:13 | LL | while let Some(i) = self.0.0.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.0.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:322:5 + --> $DIR/while_let_on_iterator.rs:323:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:334:9 + --> $DIR/while_let_on_iterator.rs:335:9 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:348:5 + --> $DIR/while_let_on_iterator.rs:349:5 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:359:5 + --> $DIR/while_let_on_iterator.rs:360:5 | LL | while let Some(x) = it.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:394:5 + --> $DIR/while_let_on_iterator.rs:395:5 | LL | while let Some(x) = s.x.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in s.x.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:401:5 + --> $DIR/while_let_on_iterator.rs:402:5 | LL | while let Some(x) = x[0].next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in x[0].by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:408:5 + --> $DIR/while_let_on_iterator.rs:410:9 + | +LL | while let Some(x) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:420:9 + | +LL | while let Some(x) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:430:9 + | +LL | while let Some(x) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:440:9 + | +LL | while let Some(x) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:450:5 | LL | while let Some(..) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` -error: aborting due to 23 previous errors +error: aborting due to 26 previous errors diff --git a/tests/ui/wildcard_imports.fixed b/tests/ui/wildcard_imports.fixed index b6f47ae906b7..ef55f1c31a88 100644 --- a/tests/ui/wildcard_imports.fixed +++ b/tests/ui/wildcard_imports.fixed @@ -199,7 +199,7 @@ mod super_imports { } } - mod should_be_replaced_futher_inside { + mod should_be_replaced_further_inside { fn insidefoo() {} mod inner { use super::insidefoo; diff --git a/tests/ui/wildcard_imports.rs b/tests/ui/wildcard_imports.rs index eb404b7a3de5..b81285142069 100644 --- a/tests/ui/wildcard_imports.rs +++ b/tests/ui/wildcard_imports.rs @@ -200,7 +200,7 @@ mod super_imports { } } - mod should_be_replaced_futher_inside { + mod should_be_replaced_further_inside { fn insidefoo() {} mod inner { use super::*; diff --git a/util/etc/vscode-tasks.json b/util/etc/vscode-tasks.json index a4bb26b9f900..ab98f9b4154a 100644 --- a/util/etc/vscode-tasks.json +++ b/util/etc/vscode-tasks.json @@ -8,15 +8,15 @@ "problemMatcher": [], "group": { "kind": "build", - "isDefault": true, - }, + "isDefault": true + } }, { "label": "cargo dev fmt", "type": "shell", "command": "cargo dev fmt", "problemMatcher": [], - "group": "none", + "group": "none" }, { "label": "cargo uitest", @@ -24,19 +24,19 @@ "command": "cargo uitest", "options": { "env": { - "RUST_BACKTRACE": "1", // This task will usually execute all UI tests inside `tests/ui` you can // optionally uncomment the line below and only run a specific test. // - // See: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md#testing + // See: https://github.com/rust-lang/rust-clippy/blob/master/book/src/development/adding_lints.md#testing // // "TESTNAME": "", - }, + "RUST_BACKTRACE": "1" + } }, "problemMatcher": [], "group": { "kind": "test", - "isDefault": true, + "isDefault": true } }, { @@ -44,14 +44,14 @@ "type": "shell", "command": "cargo test", "problemMatcher": [], - "group": "test", + "group": "test" }, { "label": "cargo dev bless", "type": "shell", "command": "cargo dev bless", "problemMatcher": [], - "group": "none", - }, - ], + "group": "none" + } + ] } diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 6183089911bd..c5d602ea3035 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -15,7 +15,8 @@ Otherwise, have a great day =^.^= Clippy Lints - + + @@ -561,8 +562,8 @@ Otherwise, have a great day =^.^= - - + + diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 6909fbcae09f..366e7c8843f2 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -7,7 +7,7 @@ if (lang && hljs.getLanguage(lang)) { try { return '
' +
-                        hljs.highlight(lang, str, true).value +
+                        hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
                         '
'; } catch (__) {} } @@ -365,6 +365,9 @@ function setTheme(theme, store) { document.getElementsByTagName("body")[0].className = theme; + document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; + document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; + document.getElementById("styleHighlight").disabled = !enableHighlight; document.getElementById("styleNight").disabled = !enableNight; document.getElementById("styleAyu").disabled = !enableAyu; From 659a9ac8bf3eb2e40137b12469a7e6da3e47b333 Mon Sep 17 00:00:00 2001 From: Joel Montes de Oca <6587811+JoelMon@users.noreply.github.com> Date: Thu, 14 Jul 2022 10:20:08 -0400 Subject: [PATCH 08/42] Rephrased text to remove passive voice for a more active one. Co-authored-by: Alex <69764315+Serial-ATA@users.noreply.github.com> --- book/src/continuous_integration/github_actions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md index 42a43ef13801..5c8280f17c83 100644 --- a/book/src/continuous_integration/github_actions.md +++ b/book/src/continuous_integration/github_actions.md @@ -1,7 +1,7 @@ # GitHub Actions -On the GitHub hosted runners, Clippy from the latest stable Rust version comes -pre-installed. So all you have to do is to run `cargo clippy`. +GitHub hosted runners using the latest stable version of Rust have Clippy pre-installed. +It is as simple as running `cargo clippy` to run lints against the codebase. ```yml on: push From 7a5965b459ecbaa3ca702ebb9ae31b89ef449528 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Mon, 18 Jul 2022 10:36:48 +0200 Subject: [PATCH 09/42] unused_self: respect avoid-breaking-exported-api --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/unused_self.rs | 17 +++++++++++++++-- clippy_lints/src/utils/conf.rs | 2 +- tests/ui/unused_self.rs | 9 +++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4f9db4eb5ae0..8444532f6bbf 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -782,7 +782,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: )) }); store.register_late_pass(|| Box::new(default::Default::default())); - store.register_late_pass(|| Box::new(unused_self::UnusedSelf)); + store.register_late_pass(move || Box::new(unused_self::UnusedSelf::new(avoid_breaking_exported_api))); store.register_late_pass(|| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)); store.register_late_pass(|| Box::new(exit::Exit)); store.register_late_pass(|| Box::new(to_digit_is_some::ToDigitIsSome)); diff --git a/clippy_lints/src/unused_self.rs b/clippy_lints/src/unused_self.rs index fd9d5b52e501..51c65d898cf5 100644 --- a/clippy_lints/src/unused_self.rs +++ b/clippy_lints/src/unused_self.rs @@ -3,7 +3,7 @@ use clippy_utils::visitors::is_local_used; use if_chain::if_chain; use rustc_hir::{Impl, ImplItem, ImplItemKind, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; declare_clippy_lint! { /// ### What it does @@ -35,7 +35,19 @@ declare_clippy_lint! { "methods that contain a `self` argument but don't use it" } -declare_lint_pass!(UnusedSelf => [UNUSED_SELF]); +pub struct UnusedSelf { + avoid_breaking_exported_api: bool, +} + +impl_lint_pass!(UnusedSelf => [UNUSED_SELF]); + +impl UnusedSelf { + pub fn new(avoid_breaking_exported_api: bool) -> Self { + Self { + avoid_breaking_exported_api, + } + } +} impl<'tcx> LateLintPass<'tcx> for UnusedSelf { fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &ImplItem<'_>) { @@ -49,6 +61,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf { if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind; if assoc_item.fn_has_self_parameter; if let ImplItemKind::Fn(.., body_id) = &impl_item.kind; + if !cx.access_levels.is_exported(impl_item.def_id) || !self.avoid_breaking_exported_api; let body = cx.tcx.hir().body(*body_id); if let [self_param, ..] = body.params; if !is_local_used(cx, body, self_param.pat.hir_id); diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 38e5c5e5b736..6153dbbc88d1 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -191,7 +191,7 @@ macro_rules! define_Conf { } define_Conf! { - /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. + /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. /// /// Suppress lints whenever the suggested change would cause breakage for other crates. (avoid_breaking_exported_api: bool = true), diff --git a/tests/ui/unused_self.rs b/tests/ui/unused_self.rs index 08bf58fec7c3..92e8e1dba69d 100644 --- a/tests/ui/unused_self.rs +++ b/tests/ui/unused_self.rs @@ -53,8 +53,17 @@ mod unused_self_allow { // shouldn't trigger fn unused_self_move(self) {} } + + pub struct D; + + impl D { + // shouldn't trigger for public methods + pub fn unused_self_move(self) {} + } } +pub use unused_self_allow::D; + mod used_self { use std::pin::Pin; From c94ca1c625ba195964ead7e1eac640563526c20b Mon Sep 17 00:00:00 2001 From: Paul <34397452+PaulTheNeko@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:24:30 +0200 Subject: [PATCH 10/42] Fix typo in alloc_instead_of_core The description previously claimed it ensures items are imported from alloc, to ensure a crate won't require alloc, which can't be true. --- clippy_lints/src/std_instead_of_core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 56f2a7bae152..560a31645637 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -63,7 +63,7 @@ declare_clippy_lint! { /// ### Why is this bad? /// /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are - /// imported from alloc to ensure disabling `alloc` does not cause the crate to fail to compile. This lint + /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint /// is also useful for crates migrating to become `no_std` compatible. /// /// ### Example From 31e5465f8a0e35b8f9e8e28d359e4098e9f44e4f Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 18 Jul 2022 14:29:45 -0300 Subject: [PATCH 11/42] Add Arithmetic lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_restriction.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/operators/arithmetic.rs | 119 ++++++++++++++++++ clippy_lints/src/operators/mod.rs | 39 ++++++ clippy_lints/src/utils/conf.rs | 4 + .../arithmetic_allowed/arithmetic_allowed.rs | 24 ++++ tests/ui-toml/arithmetic_allowed/clippy.toml | 1 + .../toml_unknown_key/conf_unknown_key.stderr | 1 + tests/ui/arithmetic.fixed | 27 ++++ tests/ui/arithmetic.rs | 27 ++++ 12 files changed, 247 insertions(+) create mode 100644 clippy_lints/src/operators/arithmetic.rs create mode 100644 tests/ui-toml/arithmetic_allowed/arithmetic_allowed.rs create mode 100644 tests/ui-toml/arithmetic_allowed/clippy.toml create mode 100644 tests/ui/arithmetic.fixed create mode 100644 tests/ui/arithmetic.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5c216c154a..aeaf39756495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3437,6 +3437,7 @@ Released 2018-09-13 [`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant +[`arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions [`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore [`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index d40c6aec04ae..1ffd190cb9b3 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -418,6 +418,7 @@ store.register_lints(&[ only_used_in_recursion::ONLY_USED_IN_RECURSION, open_options::NONSENSICAL_OPEN_OPTIONS, operators::ABSURD_EXTREME_COMPARISONS, + operators::ARITHMETIC, operators::ASSIGN_OP_PATTERN, operators::BAD_BIT_MASK, operators::CMP_NAN, diff --git a/clippy_lints/src/lib.register_restriction.rs b/clippy_lints/src/lib.register_restriction.rs index 43f1c892eb9b..495abd8387e8 100644 --- a/clippy_lints/src/lib.register_restriction.rs +++ b/clippy_lints/src/lib.register_restriction.rs @@ -48,6 +48,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve LintId::of(mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION), LintId::of(module_style::MOD_MODULE_FILES), LintId::of(module_style::SELF_NAMED_MODULE_FILES), + LintId::of(operators::ARITHMETIC), LintId::of(operators::FLOAT_ARITHMETIC), LintId::of(operators::FLOAT_CMP_CONST), LintId::of(operators::INTEGER_ARITHMETIC), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8444532f6bbf..8ac40cc73e01 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -548,6 +548,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(utils::internal_lints::MsrvAttrImpl)); } + let arithmetic_allowed = conf.arithmetic_allowed.clone(); + store.register_late_pass(move || Box::new(operators::arithmetic::Arithmetic::new(arithmetic_allowed.clone()))); store.register_late_pass(|| Box::new(utils::dump_hir::DumpHir)); store.register_late_pass(|| Box::new(utils::author::Author)); let await_holding_invalid_types = conf.await_holding_invalid_types.clone(); diff --git a/clippy_lints/src/operators/arithmetic.rs b/clippy_lints/src/operators/arithmetic.rs new file mode 100644 index 000000000000..800cf249f5c7 --- /dev/null +++ b/clippy_lints/src/operators/arithmetic.rs @@ -0,0 +1,119 @@ +#![allow( + // False positive + clippy::match_same_arms +)] + +use super::ARITHMETIC; +use clippy_utils::{consts::constant_simple, diagnostics::span_lint}; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::source_map::Span; + +const HARD_CODED_ALLOWED: &[&str] = &["std::num::Saturating", "std::string::String", "std::num::Wrapping"]; + +#[derive(Debug)] +pub struct Arithmetic { + allowed: FxHashSet, + // Used to check whether expressions are constants, such as in enum discriminants and consts + const_span: Option, + expr_span: Option, +} + +impl_lint_pass!(Arithmetic => [ARITHMETIC]); + +impl Arithmetic { + #[must_use] + pub fn new(mut allowed: FxHashSet) -> Self { + allowed.extend(HARD_CODED_ALLOWED.iter().copied().map(String::from)); + Self { + allowed, + const_span: None, + expr_span: None, + } + } + + /// Checks if the given `expr` has any of the inner `allowed` elements. + fn is_allowed_ty(&self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool { + self.allowed.contains( + cx.typeck_results() + .expr_ty(expr) + .to_string() + .split('<') + .next() + .unwrap_or_default(), + ) + } + + fn issue_lint(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { + span_lint(cx, ARITHMETIC, expr.span, "arithmetic detected"); + self.expr_span = Some(expr.span); + } +} + +impl<'tcx> LateLintPass<'tcx> for Arithmetic { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if self.expr_span.is_some() { + return; + } + if let Some(span) = self.const_span && span.contains(expr.span) { + return; + } + match &expr.kind { + hir::ExprKind::Binary(op, lhs, rhs) | hir::ExprKind::AssignOp(op, lhs, rhs) => { + let ( + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Mul + | hir::BinOpKind::Div + | hir::BinOpKind::Rem + | hir::BinOpKind::Shl + | hir::BinOpKind::Shr + ) = op.node else { + return; + }; + if self.is_allowed_ty(cx, lhs) || self.is_allowed_ty(cx, rhs) { + return; + } + self.issue_lint(cx, expr); + }, + hir::ExprKind::Unary(hir::UnOp::Neg, _) => { + // CTFE already takes care of things like `-1` that do not overflow. + if constant_simple(cx, cx.typeck_results(), expr).is_none() { + self.issue_lint(cx, expr); + } + }, + _ => {}, + } + } + + fn check_body(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner_def_id(body.id()); + match cx.tcx.hir().body_owner_kind(body_owner) { + hir::BodyOwnerKind::Const | hir::BodyOwnerKind::Static(_) => { + let body_span = cx.tcx.def_span(body_owner); + if let Some(span) = self.const_span && span.contains(body_span) { + return; + } + self.const_span = Some(body_span); + }, + hir::BodyOwnerKind::Closure | hir::BodyOwnerKind::Fn => {}, + } + } + + fn check_body_post(&mut self, cx: &LateContext<'_>, body: &hir::Body<'_>) { + let body_owner = cx.tcx.hir().body_owner(body.id()); + let body_span = cx.tcx.hir().span(body_owner); + if let Some(span) = self.const_span && span.contains(body_span) { + return; + } + self.const_span = None; + } + + fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + if Some(expr.span) == self.expr_span { + self.expr_span = None; + } + } +} diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 35fe405bcf14..c688d94bb520 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -2,6 +2,8 @@ use rustc_hir::{Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; +pub(crate) mod arithmetic; + mod absurd_extreme_comparisons; mod assign_op_pattern; mod bit_mask; @@ -57,6 +59,42 @@ declare_clippy_lint! { "a comparison with a maximum or minimum value that is always true or false" } +declare_clippy_lint! { + /// ### What it does + /// Checks for any kind of arithmetic operation of any type. + /// + /// Operators like `+`, `-`, `*` or `<<` are usually capable of overflowing according to the [Rust + /// Reference](https://doc.rust-lang.org/reference/expressions/operator-expr.html#overflow), + /// or can panic (`/`, `%`). Known safe built-in types like `Wrapping` or `Saturing` are filtered + /// away. + /// + /// ### Why is this bad? + /// Integer overflow will trigger a panic in debug builds or will wrap in + /// release mode. Division by zero will cause a panic in either mode. In some applications one + /// wants explicitly checked, wrapping or saturating arithmetic. + /// + /// #### Example + /// ```rust + /// # let a = 0; + /// a + 1; + /// ``` + /// + /// Third-party types also tend to overflow. + /// + /// #### Example + /// ```ignore,rust + /// use rust_decimal::Decimal; + /// let _n = Decimal::MAX + Decimal::MAX; + /// ``` + /// + /// ### Allowed types + /// Custom allowed types can be specified through the "arithmetic-allowed" filter. + #[clippy::version = "1.64.0"] + pub ARITHMETIC, + restriction, + "any arithmetic expression that could overflow or panic" +} + declare_clippy_lint! { /// ### What it does /// Checks for integer arithmetic operations which could overflow or panic. @@ -747,6 +785,7 @@ pub struct Operators { } impl_lint_pass!(Operators => [ ABSURD_EXTREME_COMPARISONS, + ARITHMETIC, INTEGER_ARITHMETIC, FLOAT_ARITHMETIC, ASSIGN_OP_PATTERN, diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 6153dbbc88d1..6e033b3be2d8 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -191,6 +191,10 @@ macro_rules! define_Conf { } define_Conf! { + /// Lint: Arithmetic. + /// + /// Suppress checking of the passed type names. + (arithmetic_allowed: rustc_data_structures::fx::FxHashSet = <_>::default()), /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX. /// /// Suppress lints whenever the suggested change would cause breakage for other crates. diff --git a/tests/ui-toml/arithmetic_allowed/arithmetic_allowed.rs b/tests/ui-toml/arithmetic_allowed/arithmetic_allowed.rs new file mode 100644 index 000000000000..195fabdbf710 --- /dev/null +++ b/tests/ui-toml/arithmetic_allowed/arithmetic_allowed.rs @@ -0,0 +1,24 @@ +#![warn(clippy::arithmetic)] + +use core::ops::Add; + +#[derive(Clone, Copy)] +struct Point { + x: i32, + y: i32, +} + +impl Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + todo!() + } +} + +fn main() { + let _ = Point { x: 1, y: 0 } + Point { x: 2, y: 3 }; + + let point: Point = Point { x: 1, y: 0 }; + let _ = point + point; +} diff --git a/tests/ui-toml/arithmetic_allowed/clippy.toml b/tests/ui-toml/arithmetic_allowed/clippy.toml new file mode 100644 index 000000000000..cc40570b12a0 --- /dev/null +++ b/tests/ui-toml/arithmetic_allowed/clippy.toml @@ -0,0 +1 @@ +arithmetic-allowed = ["Point"] diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 1d87fd91a253..fe5139c47680 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -3,6 +3,7 @@ error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown fie allow-expect-in-tests allow-unwrap-in-tests allowed-scripts + arithmetic-allowed array-size-threshold avoid-breaking-exported-api await-holding-invalid-types diff --git a/tests/ui/arithmetic.fixed b/tests/ui/arithmetic.fixed new file mode 100644 index 000000000000..a2a1c4394c21 --- /dev/null +++ b/tests/ui/arithmetic.fixed @@ -0,0 +1,27 @@ +// run-rustfix + +#![allow(clippy::unnecessary_owned_empty_strings)] +#![feature(saturating_int_impl)] +#![warn(clippy::arithmetic)] + +use core::num::{Saturating, Wrapping}; + +pub fn hard_coded_allowed() { + let _ = Saturating(0u32) + Saturating(0u32); + let _ = String::new() + ""; + let _ = Wrapping(0u32) + Wrapping(0u32); + + let saturating: Saturating = Saturating(0u32); + let string: String = String::new(); + let wrapping: Wrapping = Wrapping(0u32); + + let inferred_saturating = saturating + saturating; + let inferred_string = string + ""; + let inferred_wrapping = wrapping + wrapping; + + let _ = inferred_saturating + inferred_saturating; + let _ = inferred_string + ""; + let _ = inferred_wrapping + inferred_wrapping; +} + +fn main() {} diff --git a/tests/ui/arithmetic.rs b/tests/ui/arithmetic.rs new file mode 100644 index 000000000000..a2a1c4394c21 --- /dev/null +++ b/tests/ui/arithmetic.rs @@ -0,0 +1,27 @@ +// run-rustfix + +#![allow(clippy::unnecessary_owned_empty_strings)] +#![feature(saturating_int_impl)] +#![warn(clippy::arithmetic)] + +use core::num::{Saturating, Wrapping}; + +pub fn hard_coded_allowed() { + let _ = Saturating(0u32) + Saturating(0u32); + let _ = String::new() + ""; + let _ = Wrapping(0u32) + Wrapping(0u32); + + let saturating: Saturating = Saturating(0u32); + let string: String = String::new(); + let wrapping: Wrapping = Wrapping(0u32); + + let inferred_saturating = saturating + saturating; + let inferred_string = string + ""; + let inferred_wrapping = wrapping + wrapping; + + let _ = inferred_saturating + inferred_saturating; + let _ = inferred_string + ""; + let _ = inferred_wrapping + inferred_wrapping; +} + +fn main() {} From 30a9533570bded58970c4e5631e5348fe47f7323 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 20 Jun 2022 19:25:52 -0700 Subject: [PATCH 12/42] Mention first and last macro in backtrace --- tests/ui/diverging_sub_expression.stderr | 2 +- tests/ui/fallible_impl_from.stderr | 6 +++--- tests/ui/issue-7447.stderr | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ui/diverging_sub_expression.stderr b/tests/ui/diverging_sub_expression.stderr index c712a6a7e38e..9c91d935716d 100644 --- a/tests/ui/diverging_sub_expression.stderr +++ b/tests/ui/diverging_sub_expression.stderr @@ -36,7 +36,7 @@ error: sub-expression diverges LL | _ => true || panic!("boo"), | ^^^^^^^^^^^^^ | - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: sub-expression diverges --> $DIR/diverging_sub_expression.rs:38:26 diff --git a/tests/ui/fallible_impl_from.stderr b/tests/ui/fallible_impl_from.stderr index 4e0f08a1215c..d637dbce5d79 100644 --- a/tests/ui/fallible_impl_from.stderr +++ b/tests/ui/fallible_impl_from.stderr @@ -38,7 +38,7 @@ note: potential failure(s) | LL | panic!(); | ^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: consider implementing `TryFrom` instead --> $DIR/fallible_impl_from.rs:35:1 @@ -65,7 +65,7 @@ LL | } else if s.parse::().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: consider implementing `TryFrom` instead --> $DIR/fallible_impl_from.rs:53:1 @@ -87,7 +87,7 @@ LL | if s.parse::().ok().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/issue-7447.stderr b/tests/ui/issue-7447.stderr index 463a48b24a32..8d8c29f13858 100644 --- a/tests/ui/issue-7447.stderr +++ b/tests/ui/issue-7447.stderr @@ -5,7 +5,7 @@ LL | byte_view(panic!()); | ^^^^^^^^ | = note: `-D clippy::diverging-sub-expression` implied by `-D warnings` - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: sub-expression diverges --> $DIR/issue-7447.rs:24:19 @@ -13,7 +13,7 @@ error: sub-expression diverges LL | group_entries(panic!()); | ^^^^^^^^ | - = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors From 95c759157c56f41bc58c6f21fa2b017c34252fce Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 19 Jul 2022 09:53:00 -0400 Subject: [PATCH 13/42] Check for `todo!` on every expression in `SpanlessEq` --- clippy_utils/src/hir_utils.rs | 33 ++++----------------------------- tests/ui/match_same_arms2.rs | 10 +++++++++- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 77c974582ecb..eaf260ddfb83 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -127,9 +127,6 @@ impl HirEqInterExpr<'_, '_, '_> { /// Checks whether two blocks are the same. fn eq_block(&mut self, left: &Block<'_>, right: &Block<'_>) -> bool { - if self.cannot_be_compared_block(left) || self.cannot_be_compared_block(right) { - return false; - } match (left.stmts, left.expr, right.stmts, right.expr) { ([], None, [], None) => { // For empty blocks, check to see if the tokens are equal. This will catch the case where a macro @@ -180,36 +177,13 @@ impl HirEqInterExpr<'_, '_, '_> { } } - fn cannot_be_compared_block(&mut self, block: &Block<'_>) -> bool { - if block.stmts.last().map_or(false, |stmt| { - matches!( - stmt.kind, - StmtKind::Semi(semi_expr) if self.should_ignore(semi_expr) - ) - }) { - return true; - } - - if let Some(block_expr) = block.expr - && self.should_ignore(block_expr) - { - return true - } - - false - } - fn should_ignore(&mut self, expr: &Expr<'_>) -> bool { - if macro_backtrace(expr.span).last().map_or(false, |macro_call| { + macro_backtrace(expr.span).last().map_or(false, |macro_call| { matches!( &self.inner.cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::todo_macro | sym::unimplemented_macro) ) - }) { - return true; - } - - false + }) } pub fn eq_array_length(&mut self, left: ArrayLen, right: ArrayLen) -> bool { @@ -327,7 +301,8 @@ impl HirEqInterExpr<'_, '_, '_> { (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re), _ => false, }; - is_eq || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right)) + (is_eq && (!self.should_ignore(left) || !self.should_ignore(right))) + || self.inner.expr_fallback.as_mut().map_or(false, |f| f(left, right)) } fn eq_exprs(&mut self, left: &[Expr<'_>], right: &[Expr<'_>]) -> bool { diff --git a/tests/ui/match_same_arms2.rs b/tests/ui/match_same_arms2.rs index dbfeb4379d51..7aba5b447d55 100644 --- a/tests/ui/match_same_arms2.rs +++ b/tests/ui/match_same_arms2.rs @@ -1,5 +1,5 @@ #![warn(clippy::match_same_arms)] -#![allow(clippy::blacklisted_name)] +#![allow(clippy::blacklisted_name, clippy::diverging_sub_expression)] fn bar(_: T) {} fn foo() -> bool { @@ -227,4 +227,12 @@ fn main() { Some(Bar { y: 0, x: 5, .. }) => 1, _ => 200, }; + + let _ = match 0 { + 0 => todo!(), + 1 => todo!(), + 2 => core::convert::identity::(todo!()), + 3 => core::convert::identity::(todo!()), + _ => 5, + }; } From 40340745a80904722323845b7f93d43af1c1f83a Mon Sep 17 00:00:00 2001 From: Evan Typanski Date: Tue, 19 Jul 2022 15:17:24 -0400 Subject: [PATCH 14/42] Use parens around [`needless_borrow`] field calls --- clippy_lints/src/dereference.rs | 3 ++- tests/ui/needless_borrow.fixed | 25 +++++++++++++++++++++++++ tests/ui/needless_borrow.rs | 25 +++++++++++++++++++++++++ tests/ui/needless_borrow.stderr | 14 +++++++++++++- 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 8c7cf7748be1..a90f894a7b19 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1028,9 +1028,10 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data let mut app = Applicability::MachineApplicable; let (snip, snip_is_macro) = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app); span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, state.msg, |diag| { + let calls_field = matches!(expr.kind, ExprKind::Field(..)) && matches!(data.position, Position::Callee); let sugg = if !snip_is_macro - && expr.precedence().order() < data.position.precedence() && !has_enclosing_paren(&snip) + && (expr.precedence().order() < data.position.precedence() || calls_field) { format!("({})", snip) } else { diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index 09afe2ddbbf6..bfd2725ecaaa 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -158,3 +158,28 @@ fn check_expect_suppression() { #[expect(clippy::needless_borrow)] let _ = x(&&a); } + +#[allow(dead_code)] +mod issue9160 { + pub struct S { + f: F, + } + + impl S + where + F: Fn() -> T, + { + fn calls_field(&self) -> T { + (self.f)() + } + } + + impl S + where + F: FnMut() -> T, + { + fn calls_mut_field(&mut self) -> T { + (self.f)() + } + } +} diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 3ae4722a1f89..c457d8c54718 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -158,3 +158,28 @@ fn check_expect_suppression() { #[expect(clippy::needless_borrow)] let _ = x(&&a); } + +#[allow(dead_code)] +mod issue9160 { + pub struct S { + f: F, + } + + impl S + where + F: Fn() -> T, + { + fn calls_field(&self) -> T { + (&self.f)() + } + } + + impl S + where + F: FnMut() -> T, + { + fn calls_mut_field(&mut self) -> T { + (&mut self.f)() + } + } +} diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index 8a2e2b989590..66588689d818 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -120,5 +120,17 @@ error: this expression creates a reference which is immediately dereferenced by LL | (&&5).foo(); | ^^^^^ help: change this to: `(&5)` -error: aborting due to 20 previous errors +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:173:13 + | +LL | (&self.f)() + | ^^^^^^^^^ help: change this to: `(self.f)` + +error: this expression borrows a value the compiler would automatically borrow + --> $DIR/needless_borrow.rs:182:13 + | +LL | (&mut self.f)() + | ^^^^^^^^^^^^^ help: change this to: `(self.f)` + +error: aborting due to 22 previous errors From bcd2241c9a90c5ec7b30fb8e80edd374d2331a09 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Wed, 20 Jul 2022 07:55:58 +0000 Subject: [PATCH 15/42] Revert "Rollup merge of #98582 - oli-obk:unconstrained_opaque_type, r=estebank" This reverts commit 6f8fb911ad504b77549cf3256a09465621beab9d, reversing changes made to 7210e46dc69a4b197a313d093fe145722c248b7d. --- clippy_utils/src/qualify_min_const_fn.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 9690ad277717..3bf75bcbee83 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -252,7 +252,6 @@ fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &B } }, ProjectionElem::ConstantIndex { .. } - | ProjectionElem::OpaqueCast(..) | ProjectionElem::Downcast(..) | ProjectionElem::Subslice { .. } | ProjectionElem::Deref From a2f9b9311656afd18468f242db3507157371c545 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 20 Jul 2022 10:00:04 -0400 Subject: [PATCH 16/42] Don't lint `assign_op_pattern` when the suggestion would cause borrowck errors --- .../src/operators/assign_op_pattern.rs | 80 +++++++++++++++++++ tests/ui/assign_ops.fixed | 11 +++ tests/ui/assign_ops.rs | 11 +++ tests/ui/assign_ops.stderr | 32 +++++--- 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/clippy_lints/src/operators/assign_op_pattern.rs b/clippy_lints/src/operators/assign_op_pattern.rs index 979e0a66707d..945a09a647c4 100644 --- a/clippy_lints/src/operators/assign_op_pattern.rs +++ b/clippy_lints/src/operators/assign_op_pattern.rs @@ -8,6 +8,10 @@ use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_lint::LateContext; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty::BorrowKind; +use rustc_trait_selection::infer::TyCtxtInferExt; +use rustc_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use super::ASSIGN_OP_PATTERN; @@ -29,6 +33,16 @@ pub(super) fn check<'tcx>( .map_or(true, |t| t.path.res.def_id() != trait_id); if implements_trait(cx, ty, trait_id, &[rty.into()]); then { + // Primitive types execute assign-ops right-to-left. Every other type is left-to-right. + if !(ty.is_primitive() && rty.is_primitive()) { + // TODO: This will have false negatives as it doesn't check if the borrows are + // actually live at the end of their respective expressions. + let mut_borrows = mut_borrows_in_expr(cx, assignee); + let imm_borrows = imm_borrows_in_expr(cx, rhs); + if mut_borrows.iter().any(|id| imm_borrows.contains(id)) { + return; + } + } span_lint_and_then( cx, ASSIGN_OP_PATTERN, @@ -99,3 +113,69 @@ impl<'a, 'tcx> Visitor<'tcx> for ExprVisitor<'a, 'tcx> { walk_expr(self, expr); } } + +fn imm_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet { + struct S(hir::HirIdSet); + impl Delegate<'_> for S { + fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) { + if matches!(kind, BorrowKind::ImmBorrow | BorrowKind::UniqueImmBorrow) { + self.0.insert(match place.place.base { + PlaceBase::Local(id) => id, + PlaceBase::Upvar(id) => id.var_path.hir_id, + _ => return, + }); + } + } + + fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {} + fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + } + + let mut s = S(hir::HirIdSet::default()); + cx.tcx.infer_ctxt().enter(|infcx| { + let mut v = ExprUseVisitor::new( + &mut s, + &infcx, + cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + cx.param_env, + cx.typeck_results(), + ); + v.consume_expr(e); + }); + s.0 +} + +fn mut_borrows_in_expr(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> hir::HirIdSet { + struct S(hir::HirIdSet); + impl Delegate<'_> for S { + fn borrow(&mut self, place: &PlaceWithHirId<'_>, _: hir::HirId, kind: BorrowKind) { + if matches!(kind, BorrowKind::MutBorrow) { + self.0.insert(match place.place.base { + PlaceBase::Local(id) => id, + PlaceBase::Upvar(id) => id.var_path.hir_id, + _ => return, + }); + } + } + + fn consume(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + fn mutate(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + fn fake_read(&mut self, _: &PlaceWithHirId<'_>, _: FakeReadCause, _: hir::HirId) {} + fn copy(&mut self, _: &PlaceWithHirId<'_>, _: hir::HirId) {} + } + + let mut s = S(hir::HirIdSet::default()); + cx.tcx.infer_ctxt().enter(|infcx| { + let mut v = ExprUseVisitor::new( + &mut s, + &infcx, + cx.tcx.hir().body_owner_def_id(cx.enclosing_body.unwrap()), + cx.param_env, + cx.typeck_results(), + ); + v.consume_expr(e); + }); + s.0 +} diff --git a/tests/ui/assign_ops.fixed b/tests/ui/assign_ops.fixed index 52b1b3afe16f..da034b51cfdb 100644 --- a/tests/ui/assign_ops.fixed +++ b/tests/ui/assign_ops.fixed @@ -1,5 +1,7 @@ // run-rustfix +use core::num::Wrapping; + #[allow(dead_code, unused_assignments)] #[warn(clippy::assign_op_pattern)] fn main() { @@ -18,4 +20,13 @@ fn main() { a = 6 << a; let mut s = String::new(); s += "bla"; + + // Issue #9180 + let mut a = Wrapping(0u32); + a += Wrapping(1u32); + let mut v = vec![0u32, 1u32]; + v[0] += v[1]; + let mut v = vec![Wrapping(0u32), Wrapping(1u32)]; + v[0] = v[0] + v[1]; + let _ = || v[0] = v[0] + v[1]; } diff --git a/tests/ui/assign_ops.rs b/tests/ui/assign_ops.rs index 527a46b2c2bb..337bb02c8a61 100644 --- a/tests/ui/assign_ops.rs +++ b/tests/ui/assign_ops.rs @@ -1,5 +1,7 @@ // run-rustfix +use core::num::Wrapping; + #[allow(dead_code, unused_assignments)] #[warn(clippy::assign_op_pattern)] fn main() { @@ -18,4 +20,13 @@ fn main() { a = 6 << a; let mut s = String::new(); s = s + "bla"; + + // Issue #9180 + let mut a = Wrapping(0u32); + a = a + Wrapping(1u32); + let mut v = vec![0u32, 1u32]; + v[0] = v[0] + v[1]; + let mut v = vec![Wrapping(0u32), Wrapping(1u32)]; + v[0] = v[0] + v[1]; + let _ = || v[0] = v[0] + v[1]; } diff --git a/tests/ui/assign_ops.stderr b/tests/ui/assign_ops.stderr index 3486bd8da4d0..63a938ab4b43 100644 --- a/tests/ui/assign_ops.stderr +++ b/tests/ui/assign_ops.stderr @@ -1,5 +1,5 @@ error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:7:5 + --> $DIR/assign_ops.rs:9:5 | LL | a = a + 1; | ^^^^^^^^^ help: replace it with: `a += 1` @@ -7,52 +7,64 @@ LL | a = a + 1; = note: `-D clippy::assign-op-pattern` implied by `-D warnings` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:8:5 + --> $DIR/assign_ops.rs:10:5 | LL | a = 1 + a; | ^^^^^^^^^ help: replace it with: `a += 1` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:9:5 + --> $DIR/assign_ops.rs:11:5 | LL | a = a - 1; | ^^^^^^^^^ help: replace it with: `a -= 1` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:10:5 + --> $DIR/assign_ops.rs:12:5 | LL | a = a * 99; | ^^^^^^^^^^ help: replace it with: `a *= 99` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:11:5 + --> $DIR/assign_ops.rs:13:5 | LL | a = 42 * a; | ^^^^^^^^^^ help: replace it with: `a *= 42` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:12:5 + --> $DIR/assign_ops.rs:14:5 | LL | a = a / 2; | ^^^^^^^^^ help: replace it with: `a /= 2` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:13:5 + --> $DIR/assign_ops.rs:15:5 | LL | a = a % 5; | ^^^^^^^^^ help: replace it with: `a %= 5` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:14:5 + --> $DIR/assign_ops.rs:16:5 | LL | a = a & 1; | ^^^^^^^^^ help: replace it with: `a &= 1` error: manual implementation of an assign operation - --> $DIR/assign_ops.rs:20:5 + --> $DIR/assign_ops.rs:22:5 | LL | s = s + "bla"; | ^^^^^^^^^^^^^ help: replace it with: `s += "bla"` -error: aborting due to 9 previous errors +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:26:5 + | +LL | a = a + Wrapping(1u32); + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `a += Wrapping(1u32)` + +error: manual implementation of an assign operation + --> $DIR/assign_ops.rs:28:5 + | +LL | v[0] = v[0] + v[1]; + | ^^^^^^^^^^^^^^^^^^ help: replace it with: `v[0] += v[1]` + +error: aborting due to 11 previous errors From 27d49b08d501512909cf6d950cb3ee24b6603490 Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Wed, 20 Jul 2022 21:41:12 +0300 Subject: [PATCH 17/42] Enable test for entrypoint_recursion for windows --- tests/ui/crate_level_checks/entrypoint_recursion.rs | 1 - tests/ui/crate_level_checks/entrypoint_recursion.stderr | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui/crate_level_checks/entrypoint_recursion.rs b/tests/ui/crate_level_checks/entrypoint_recursion.rs index 7ff5a16ed86c..1b3bcece6f1e 100644 --- a/tests/ui/crate_level_checks/entrypoint_recursion.rs +++ b/tests/ui/crate_level_checks/entrypoint_recursion.rs @@ -1,5 +1,4 @@ // ignore-macos -// ignore-windows #![feature(rustc_attrs)] diff --git a/tests/ui/crate_level_checks/entrypoint_recursion.stderr b/tests/ui/crate_level_checks/entrypoint_recursion.stderr index f52fc949f6c3..459cf12a1c20 100644 --- a/tests/ui/crate_level_checks/entrypoint_recursion.stderr +++ b/tests/ui/crate_level_checks/entrypoint_recursion.stderr @@ -1,5 +1,5 @@ error: recursing into entrypoint `a` - --> $DIR/entrypoint_recursion.rs:11:5 + --> $DIR/entrypoint_recursion.rs:10:5 | LL | a(); | ^ From 8dfea7436746c7adce35504609c8ed0f3c59478b Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Wed, 20 Jul 2022 21:35:11 -0400 Subject: [PATCH 18/42] Remove extra newlines in [`significant_drop_in_scrutinee`] docs --- clippy_lints/src/matches/mod.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index d55082c66dc8..a3d69a4d09c8 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -793,18 +793,13 @@ declare_clippy_lint! { /// ### Example /// ```rust,ignore /// # use std::sync::Mutex; - /// /// # struct State {} - /// /// # impl State { /// # fn foo(&self) -> bool { /// # true /// # } - /// /// # fn bar(&self) {} /// # } - /// - /// /// let mutex = Mutex::new(State {}); /// /// match mutex.lock().unwrap().foo() { @@ -815,22 +810,17 @@ declare_clippy_lint! { /// }; /// /// println!("All done!"); - /// /// ``` /// Use instead: /// ```rust /// # use std::sync::Mutex; - /// /// # struct State {} - /// /// # impl State { /// # fn foo(&self) -> bool { /// # true /// # } - /// /// # fn bar(&self) {} /// # } - /// /// let mutex = Mutex::new(State {}); /// /// let is_foo = mutex.lock().unwrap().foo(); From 6620aa05832a3a3d600e49f61a807945bd8f6037 Mon Sep 17 00:00:00 2001 From: Max Baumann Date: Fri, 22 Jul 2022 16:39:45 +0200 Subject: [PATCH 19/42] update case_sensitive_file_extension_comparisons example The old example does not work in case the delimiter is not in the name Co-authored-by: flip1995 <9744647+flip1995@users.noreply.github.com> --- .../src/case_sensitive_file_extension_comparisons.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs index 7af200708ff0..7eff71d50074 100644 --- a/clippy_lints/src/case_sensitive_file_extension_comparisons.rs +++ b/clippy_lints/src/case_sensitive_file_extension_comparisons.rs @@ -24,7 +24,10 @@ declare_clippy_lint! { /// Use instead: /// ```rust /// fn is_rust_file(filename: &str) -> bool { - /// filename.rsplit('.').next().map(|ext| ext.eq_ignore_ascii_case("rs")) == Some(true) + /// let filename = std::path::Path::new(filename); + /// filename.extension() + /// .map(|ext| ext.eq_ignore_ascii_case("rs")) + /// .unwrap_or(false) /// } /// ``` #[clippy::version = "1.51.0"] From 632f9945d60c6338a38d385c59c6c9b5f3f5e94d Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Fri, 15 Jul 2022 05:37:32 +0000 Subject: [PATCH 20/42] Do not resolve associated const when there is no provided value --- tests/ui/crashes/ice-6252.stderr | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/ui/crashes/ice-6252.stderr b/tests/ui/crashes/ice-6252.stderr index a1e37e7317b2..638e4a548493 100644 --- a/tests/ui/crashes/ice-6252.stderr +++ b/tests/ui/crashes/ice-6252.stderr @@ -30,15 +30,7 @@ LL | const VAL: T; LL | impl TypeVal for Multiply where N: TypeVal {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `VAL` in implementation -error: constant expression depends on a generic parameter - --> $DIR/ice-6252.rs:13:9 - | -LL | [1; >::VAL]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this may fail depending on what value the parameter takes - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors Some errors have detailed explanations: E0046, E0412. For more information about an error, try `rustc --explain E0046`. From a7389556f86bec7d31ee8b8cce5358f9af030cf0 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 23 Jul 2022 08:36:26 -0400 Subject: [PATCH 21/42] fix outdated lint names in docs --- clippy_lints/src/as_conversions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index 6e5c8f445818..c7a76e5f9077 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Note that this lint is specialized in linting *every single* use of `as` /// regardless of whether good alternatives exist or not. /// If you want more precise lints for `as`, please consider using these separate lints: - /// `unnecessary_cast`, `cast_lossless/possible_truncation/possible_wrap/precision_loss/sign_loss`, + /// `unnecessary_cast`, `cast_lossless/cast_possible_truncation/cast_possible_wrap/cast_precision_loss/cast_sign_loss`, /// `fn_to_numeric_cast(_with_truncation)`, `char_lit_as_u8`, `ref_to_mut` and `ptr_as_ptr`. /// There is a good explanation the reason why this lint should work in this way and how it is useful /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). From 663cddc2106abded203b09c868fc232e9e36b199 Mon Sep 17 00:00:00 2001 From: alexey semenyuk Date: Sun, 24 Jul 2022 11:22:50 +0300 Subject: [PATCH 22/42] Fix src link --- book/src/development/infrastructure/book.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/development/infrastructure/book.md b/book/src/development/infrastructure/book.md index b62314c6735a..a48742191850 100644 --- a/book/src/development/infrastructure/book.md +++ b/book/src/development/infrastructure/book.md @@ -25,7 +25,7 @@ instructions for other options. ## Make changes The book's -[src](https://github.com/joshrotenberg/rust-clippy/tree/clippy_guide/book/src) +[src](https://github.com/rust-lang/rust-clippy/tree/master/book/src) directory contains all of the markdown files used to generate the book. If you want to see your changes in real time, you can use the mdbook `serve` command to run a web server locally that will automatically update changes as they are From d60d5e8bde9650f967f511f6649bc8f8ece8decc Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Sun, 24 Jul 2022 16:37:08 +0000 Subject: [PATCH 23/42] Fix `useless_format` spans for `format!("{foo}")` --- clippy_lints/src/format.rs | 19 ++----------------- tests/ui/format.fixed | 6 ++++++ tests/ui/format.rs | 6 ++++++ tests/ui/format.stderr | 14 +++++++++++++- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/clippy_lints/src/format.rs b/clippy_lints/src/format.rs index 3084c70589fa..9137d8ef382b 100644 --- a/clippy_lints/src/format.rs +++ b/clippy_lints/src/format.rs @@ -9,7 +9,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::kw; -use rustc_span::{sym, BytePos, Span}; +use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does @@ -85,22 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessFormat { ExprKind::MethodCall(path, ..) => path.ident.name.as_str() == "to_string", _ => false, }; - let sugg = if format_args.format_string_span.contains(value.span) { - // Implicit argument. e.g. `format!("{x}")` span points to `{x}` - let spdata = value.span.data(); - let span = Span::new( - spdata.lo + BytePos(1), - spdata.hi - BytePos(1), - spdata.ctxt, - spdata.parent - ); - let snip = snippet_with_applicability(cx, span, "..", &mut applicability); - if is_new_string { - snip.into() - } else { - format!("{snip}.to_string()") - } - } else if is_new_string { + let sugg = if is_new_string { snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned() } else { let sugg = Sugg::hir_with_applicability(cx, value, "", &mut applicability); diff --git a/tests/ui/format.fixed b/tests/ui/format.fixed index d08f8f52495a..f4db2d20c713 100644 --- a/tests/ui/format.fixed +++ b/tests/ui/format.fixed @@ -84,4 +84,10 @@ fn main() { let _ = x.to_string(); let _ = format!("{x:?}"); // Don't lint on debug let _ = x.to_string(); + + // Issue #9234 + let abc = "abc"; + let _ = abc.to_string(); + let xx = "xx"; + let _ = xx.to_string(); } diff --git a/tests/ui/format.rs b/tests/ui/format.rs index 4a10b580d260..bf687cb1e96c 100644 --- a/tests/ui/format.rs +++ b/tests/ui/format.rs @@ -86,4 +86,10 @@ fn main() { let _ = format!("{x}"); let _ = format!("{x:?}"); // Don't lint on debug let _ = format!("{y}", y = x); + + // Issue #9234 + let abc = "abc"; + let _ = format!("{abc}"); + let xx = "xx"; + let _ = format!("{xx}"); } diff --git a/tests/ui/format.stderr b/tests/ui/format.stderr index f25c7fb1ff1c..a0f8e7d19379 100644 --- a/tests/ui/format.stderr +++ b/tests/ui/format.stderr @@ -111,5 +111,17 @@ error: useless use of `format!` LL | let _ = format!("{y}", y = x); | ^^^^^^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `x.to_string()` -error: aborting due to 17 previous errors +error: useless use of `format!` + --> $DIR/format.rs:92:13 + | +LL | let _ = format!("{abc}"); + | ^^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `abc.to_string()` + +error: useless use of `format!` + --> $DIR/format.rs:94:13 + | +LL | let _ = format!("{xx}"); + | ^^^^^^^^^^^^^^^ help: consider using `.to_string()`: `xx.to_string()` + +error: aborting due to 19 previous errors From ab6463e9d9b5c30a77879f60318b774f1e873d31 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 24 Jul 2022 18:23:33 -0400 Subject: [PATCH 24/42] Fix ICE in `miri_to_const` --- clippy_utils/src/consts.rs | 20 ++++++-------------- clippy_utils/src/lib.rs | 1 + tests/ui/crashes/ice-9238.rs | 12 ++++++++++++ 3 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 tests/ui/crashes/ice-9238.rs diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 6d4a48b53de3..351a3f4aec8c 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -619,32 +619,24 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - }, mir::ConstantKind::Val(ConstValue::ByRef { alloc, offset: _ }, _) => match result.ty().kind() { ty::Array(sub_type, len) => match sub_type.kind() { - ty::Float(FloatTy::F32) => match len.to_valtree().try_to_machine_usize(tcx) { + ty::Float(FloatTy::F32) => match len.kind().try_to_machine_usize(tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) .to_owned() - .chunks(4) - .map(|chunk| { - Some(Constant::F32(f32::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) + .array_chunks::<4>() + .map(|&chunk| Some(Constant::F32(f32::from_le_bytes(chunk)))) .collect::>>() .map(Constant::Vec), _ => None, }, - ty::Float(FloatTy::F64) => match len.to_valtree().try_to_machine_usize(tcx) { + ty::Float(FloatTy::F64) => match len.kind().try_to_machine_usize(tcx) { Some(len) => alloc .inner() .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) .to_owned() - .chunks(8) - .map(|chunk| { - Some(Constant::F64(f64::from_le_bytes( - chunk.try_into().expect("this shouldn't happen"), - ))) - }) + .array_chunks::<8>() + .map(|&chunk| Some(Constant::F64(f64::from_le_bytes(chunk)))) .collect::>>() .map(Constant::Vec), _ => None, diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 46c5c2eef567..ddc7a4d4e1dd 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(array_chunks)] #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(let_else)] diff --git a/tests/ui/crashes/ice-9238.rs b/tests/ui/crashes/ice-9238.rs new file mode 100644 index 000000000000..ee6abd519f16 --- /dev/null +++ b/tests/ui/crashes/ice-9238.rs @@ -0,0 +1,12 @@ +#![allow(incomplete_features)] +#![feature(generic_const_exprs)] +#![warn(clippy::branches_sharing_code)] + +const fn f() -> usize { + 2 +} +const C: [f64; f()] = [0f64; f()]; + +fn main() { + let _ = if true { C[0] } else { C[1] }; +} From 2255cfd2abd089d14a682c9df0cf2dd2ef697552 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 24 Jul 2022 23:12:29 -0400 Subject: [PATCH 25/42] Fix ICE in `question_mark` --- clippy_lints/src/question_mark.rs | 4 ++-- tests/ui/crashes/ice-9242.rs | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/ui/crashes/ice-9242.rs diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index f0155ed6051f..fd0a53839e6e 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -123,8 +123,8 @@ fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: if_chain! { if let Some(higher::IfLet { let_pat, let_expr, if_then, if_else }) = higher::IfLet::hir(cx, expr); if !is_else_clause(cx.tcx, expr); - if let PatKind::TupleStruct(ref path1, fields, None) = let_pat.kind; - if let PatKind::Binding(annot, bind_id, ident, _) = fields[0].kind; + if let PatKind::TupleStruct(ref path1, [field], None) = let_pat.kind; + if let PatKind::Binding(annot, bind_id, ident, _) = field.kind; let caller_ty = cx.typeck_results().expr_ty(let_expr); let if_block = IfBlockType::IfLet(path1, caller_ty, ident.name, let_expr, if_then, if_else); if (is_early_return(sym::Option, cx, &if_block) && path_to_local_id(peel_blocks(if_then), bind_id)) diff --git a/tests/ui/crashes/ice-9242.rs b/tests/ui/crashes/ice-9242.rs new file mode 100644 index 000000000000..0099e6e2f34b --- /dev/null +++ b/tests/ui/crashes/ice-9242.rs @@ -0,0 +1,8 @@ +enum E { + X(), + Y, +} + +fn main() { + let _ = if let E::X() = E::X() { 1 } else { 2 }; +} From 6bc024df188526a984827d9ecca978414df0afff Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Sun, 24 Jul 2022 20:17:55 -0400 Subject: [PATCH 26/42] Improve `[std|alloc]_instead_of_[alloc|core]` lints * Don't call `TyCtxt::crate_name` unless necessary * Don't lint on `use std::env` * Only lint once on `use std::vec` --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/std_instead_of_core.rs | 94 ++++++++++++++----------- tests/ui/std_instead_of_core.rs | 6 ++ tests/ui/std_instead_of_core.stderr | 30 +++++--- 4 files changed, 80 insertions(+), 52 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8444532f6bbf..fd4da166dfee 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -916,7 +916,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let verbose_bit_mask_threshold = conf.verbose_bit_mask_threshold; store.register_late_pass(move || Box::new(operators::Operators::new(verbose_bit_mask_threshold))); store.register_late_pass(|| Box::new(invalid_utf8_in_unchecked::InvalidUtf8InUnchecked)); - store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports)); + store.register_late_pass(|| Box::new(std_instead_of_core::StdReexports::default())); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 560a31645637..ffd63cc687a1 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -1,8 +1,8 @@ use clippy_utils::diagnostics::span_lint_and_help; use rustc_hir::{def::Res, HirId, Path, PathSegment}; -use rustc_lint::{LateContext, LateLintPass, Lint}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{sym, symbol::kw, Symbol}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{sym, symbol::kw, Span}; declare_clippy_lint! { /// ### What it does @@ -81,39 +81,55 @@ declare_clippy_lint! { "type is imported from alloc when available in core" } -declare_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); +#[derive(Default)] +pub struct StdReexports { + // Paths which can be either a module or a macro (e.g. `std::env`) will cause this check to happen + // twice. First for the mod, second for the macro. This is used to avoid the lint reporting for the macro + // when the path could be also be used to access the module. + prev_span: Span, +} +impl_lint_pass!(StdReexports => [STD_INSTEAD_OF_CORE, STD_INSTEAD_OF_ALLOC, ALLOC_INSTEAD_OF_CORE]); impl<'tcx> LateLintPass<'tcx> for StdReexports { fn check_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) { - // std_instead_of_core - check_path(cx, path, sym::std, sym::core, STD_INSTEAD_OF_CORE); - // std_instead_of_alloc - check_path(cx, path, sym::std, sym::alloc, STD_INSTEAD_OF_ALLOC); - // alloc_instead_of_core - check_path(cx, path, sym::alloc, sym::core, ALLOC_INSTEAD_OF_CORE); - } -} - -fn check_path(cx: &LateContext<'_>, path: &Path<'_>, krate: Symbol, suggested_crate: Symbol, lint: &'static Lint) { - if_chain! { - // check if path resolves to the suggested crate. - if let Res::Def(_, def_id) = path.res; - if suggested_crate == cx.tcx.crate_name(def_id.krate); - - // check if the first segment of the path is the crate we want to identify - if let Some(path_root_segment) = get_first_segment(path); - - // check if the path matches the crate we want to suggest the other path for. - if krate == path_root_segment.ident.name; - then { - span_lint_and_help( - cx, - lint, - path.span, - &format!("used import from `{}` instead of `{}`", krate, suggested_crate), - None, - &format!("consider importing the item from `{}`", suggested_crate), - ); + if let Res::Def(_, def_id) = path.res + && let Some(first_segment) = get_first_segment(path) + { + let (lint, msg, help) = match first_segment.ident.name { + sym::std => match cx.tcx.crate_name(def_id.krate) { + sym::core => ( + STD_INSTEAD_OF_CORE, + "used import from `std` instead of `core`", + "consider importing the item from `core`", + ), + sym::alloc => ( + STD_INSTEAD_OF_ALLOC, + "used import from `std` instead of `alloc`", + "consider importing the item from `alloc`", + ), + _ => { + self.prev_span = path.span; + return; + }, + }, + sym::alloc => { + if cx.tcx.crate_name(def_id.krate) == sym::core { + ( + ALLOC_INSTEAD_OF_CORE, + "used import from `alloc` instead of `core`", + "consider importing the item from `core`", + ) + } else { + self.prev_span = path.span; + return; + } + }, + _ => return, + }; + if path.span != self.prev_span { + span_lint_and_help(cx, lint, path.span, msg, None, help); + self.prev_span = path.span; + } } } } @@ -123,12 +139,10 @@ fn check_path(cx: &LateContext<'_>, path: &Path<'_>, krate: Symbol, suggested_cr /// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`] /// is returned. fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> { - let segment = path.segments.first()?; - - // A global path will have PathRoot as the first segment. In this case, return the segment after. - if segment.ident.name == kw::PathRoot { - path.segments.get(1) - } else { - Some(segment) + match path.segments { + // A global path will have PathRoot as the first segment. In this case, return the segment after. + [x, y, ..] if x.ident.name == kw::PathRoot => Some(y), + [x, ..] => Some(x), + _ => None, } } diff --git a/tests/ui/std_instead_of_core.rs b/tests/ui/std_instead_of_core.rs index 74f05ec1f658..6b27475de4c8 100644 --- a/tests/ui/std_instead_of_core.rs +++ b/tests/ui/std_instead_of_core.rs @@ -9,6 +9,8 @@ fn std_instead_of_core() { use std::hash::Hasher; // Absolute path use ::std::hash::Hash; + // Don't lint on `env` macro + use std::env; // Multiple imports use std::fmt::{Debug, Result}; @@ -20,10 +22,14 @@ fn std_instead_of_core() { // Types let cell = std::cell::Cell::new(8u32); let cell_absolute = ::std::cell::Cell::new(8u32); + + let _ = std::env!("PATH"); } #[warn(clippy::std_instead_of_alloc)] fn std_instead_of_alloc() { + // Only lint once. + use std::vec; use std::vec::Vec; } diff --git a/tests/ui/std_instead_of_core.stderr b/tests/ui/std_instead_of_core.stderr index 9f1644835c10..bc49dabf5868 100644 --- a/tests/ui/std_instead_of_core.stderr +++ b/tests/ui/std_instead_of_core.stderr @@ -16,7 +16,7 @@ LL | use ::std::hash::Hash; = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:14:20 + --> $DIR/std_instead_of_core.rs:16:20 | LL | use std::fmt::{Debug, Result}; | ^^^^^ @@ -24,7 +24,7 @@ LL | use std::fmt::{Debug, Result}; = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:14:27 + --> $DIR/std_instead_of_core.rs:16:27 | LL | use std::fmt::{Debug, Result}; | ^^^^^^ @@ -32,7 +32,7 @@ LL | use std::fmt::{Debug, Result}; = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:17:15 + --> $DIR/std_instead_of_core.rs:19:15 | LL | let ptr = std::ptr::null::(); | ^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | let ptr = std::ptr::null::(); = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:18:19 + --> $DIR/std_instead_of_core.rs:20:19 | LL | let ptr_mut = ::std::ptr::null_mut::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +48,7 @@ LL | let ptr_mut = ::std::ptr::null_mut::(); = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:21:16 + --> $DIR/std_instead_of_core.rs:23:16 | LL | let cell = std::cell::Cell::new(8u32); | ^^^^^^^^^^^^^^^ @@ -56,7 +56,7 @@ LL | let cell = std::cell::Cell::new(8u32); = help: consider importing the item from `core` error: used import from `std` instead of `core` - --> $DIR/std_instead_of_core.rs:22:25 + --> $DIR/std_instead_of_core.rs:24:25 | LL | let cell_absolute = ::std::cell::Cell::new(8u32); | ^^^^^^^^^^^^^^^^^ @@ -64,16 +64,24 @@ LL | let cell_absolute = ::std::cell::Cell::new(8u32); = help: consider importing the item from `core` error: used import from `std` instead of `alloc` - --> $DIR/std_instead_of_core.rs:27:9 + --> $DIR/std_instead_of_core.rs:32:9 | -LL | use std::vec::Vec; - | ^^^^^^^^^^^^^ +LL | use std::vec; + | ^^^^^^^^ | = note: `-D clippy::std-instead-of-alloc` implied by `-D warnings` = help: consider importing the item from `alloc` +error: used import from `std` instead of `alloc` + --> $DIR/std_instead_of_core.rs:33:9 + | +LL | use std::vec::Vec; + | ^^^^^^^^^^^^^ + | + = help: consider importing the item from `alloc` + error: used import from `alloc` instead of `core` - --> $DIR/std_instead_of_core.rs:32:9 + --> $DIR/std_instead_of_core.rs:38:9 | LL | use alloc::slice::from_ref; | ^^^^^^^^^^^^^^^^^^^^^^ @@ -81,5 +89,5 @@ LL | use alloc::slice::from_ref; = note: `-D clippy::alloc-instead-of-core` implied by `-D warnings` = help: consider importing the item from `core` -error: aborting due to 10 previous errors +error: aborting due to 11 previous errors From b5e5b4281550b1517a72549e6567494e3c3f6605 Mon Sep 17 00:00:00 2001 From: alex-semenyuk Date: Mon, 25 Jul 2022 13:57:26 +0300 Subject: [PATCH 27/42] Enable test multiple_config_files on windows --- tests/ui-cargo/multiple_config_files/warn/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ui-cargo/multiple_config_files/warn/src/main.rs b/tests/ui-cargo/multiple_config_files/warn/src/main.rs index 2d0b4a7948c4..e7a11a969c03 100644 --- a/tests/ui-cargo/multiple_config_files/warn/src/main.rs +++ b/tests/ui-cargo/multiple_config_files/warn/src/main.rs @@ -1,5 +1,3 @@ -// ignore-windows - fn main() { println!("Hello, world!"); } From 477356460b0797a3ce666f5e55cfa5fc74314c00 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:41:12 +0900 Subject: [PATCH 28/42] check macro statements in non_copy_const.rs --- Cargo.toml | 2 +- clippy_lints/src/non_copy_const.rs | 18 +++++++------ .../declare_interior_mutable_const/others.rs | 27 ++++++++++++++++--- .../others.stderr | 13 ++++++++- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 69deaca0b4b8..699e68b86f76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ serde = { version = "1.0.125", features = ["derive"] } syn = { version = "1.0", features = ["full"] } futures = "0.3" parking_lot = "0.12" -tokio = { version = "1", features = ["io-util"] } +tokio = { version = "1", features = ["full"] } rustc-semver = "1.1" [build-dependencies] diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 6bce5fbd4c1f..72c86f28bbc6 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -251,14 +251,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx Item<'_>) { if let ItemKind::Const(hir_ty, body_id) = it.kind { let ty = hir_ty_to_ty(cx.tcx, hir_ty); - if !macro_backtrace(it.span).last().map_or(false, |macro_call| { - matches!( - cx.tcx.get_diagnostic_name(macro_call.def_id), - Some(sym::thread_local_macro) - ) - }) && is_unfrozen(cx, ty) - && is_value_unfrozen_poly(cx, body_id, ty) - { + if !ignored_macro(cx, it) && is_unfrozen(cx, ty) && is_value_unfrozen_poly(cx, body_id, ty) { lint(cx, Source::Item { item: it.span }); } } @@ -445,3 +438,12 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst { } } } + +fn ignored_macro(cx: &LateContext<'_>, it: &rustc_hir::Item<'_>) -> bool { + macro_backtrace(it.span).any(|macro_call| { + matches!( + cx.tcx.get_diagnostic_name(macro_call.def_id), + Some(sym::thread_local_macro) + ) + }) +} diff --git a/tests/ui/declare_interior_mutable_const/others.rs b/tests/ui/declare_interior_mutable_const/others.rs index 62af545db503..f46d3499f4e9 100644 --- a/tests/ui/declare_interior_mutable_const/others.rs +++ b/tests/ui/declare_interior_mutable_const/others.rs @@ -31,9 +31,30 @@ const NO_ANN: &dyn Display = &70; static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); //^ there should be no lints on this line -// issue #8493 -thread_local! { - static THREAD_LOCAL: Cell = const { Cell::new(0) }; +mod issue_8493 { + use std::cell::Cell; + + // https://github.com/rust-lang/rust-clippy/issues/9224 + tokio::task_local! { + pub static _FOO: String; + } + + thread_local! { + static _BAR: Cell = const { Cell::new(0) }; + } + + macro_rules! issue_8493 { + () => { + const _BAZ: Cell = Cell::new(0); //~ ERROR interior mutable + static _FOOBAR: () = { + thread_local! { + static _VAR: Cell = const { Cell::new(0) }; + } + }; + }; + } + + issue_8493!(); } fn main() {} diff --git a/tests/ui/declare_interior_mutable_const/others.stderr b/tests/ui/declare_interior_mutable_const/others.stderr index fd0689dfc4c9..6cba9491ef4e 100644 --- a/tests/ui/declare_interior_mutable_const/others.stderr +++ b/tests/ui/declare_interior_mutable_const/others.stderr @@ -35,5 +35,16 @@ LL | declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable | = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 4 previous errors +error: a `const` item should never be interior mutable + --> $DIR/others.rs:48:13 + | +LL | const _BAZ: Cell = Cell::new(0); //~ ERROR interior mutable + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | issue_8493!(); + | ------------- in this macro invocation + | + = note: this error originates in the macro `issue_8493` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 5 previous errors From b6c301055edc9d769bb7ac8e4b8bc083e31ac748 Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Mon, 25 Jul 2022 23:04:18 +0900 Subject: [PATCH 29/42] fixed tests to not use tokio --- Cargo.toml | 2 +- tests/ui/declare_interior_mutable_const/others.rs | 5 ----- tests/ui/declare_interior_mutable_const/others.stderr | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 699e68b86f76..69deaca0b4b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ serde = { version = "1.0.125", features = ["derive"] } syn = { version = "1.0", features = ["full"] } futures = "0.3" parking_lot = "0.12" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["io-util"] } rustc-semver = "1.1" [build-dependencies] diff --git a/tests/ui/declare_interior_mutable_const/others.rs b/tests/ui/declare_interior_mutable_const/others.rs index f46d3499f4e9..896596b56792 100644 --- a/tests/ui/declare_interior_mutable_const/others.rs +++ b/tests/ui/declare_interior_mutable_const/others.rs @@ -34,11 +34,6 @@ static STATIC_TUPLE: (AtomicUsize, String) = (ATOMIC, STRING); mod issue_8493 { use std::cell::Cell; - // https://github.com/rust-lang/rust-clippy/issues/9224 - tokio::task_local! { - pub static _FOO: String; - } - thread_local! { static _BAR: Cell = const { Cell::new(0) }; } diff --git a/tests/ui/declare_interior_mutable_const/others.stderr b/tests/ui/declare_interior_mutable_const/others.stderr index 6cba9491ef4e..1fd6d7322a76 100644 --- a/tests/ui/declare_interior_mutable_const/others.stderr +++ b/tests/ui/declare_interior_mutable_const/others.stderr @@ -36,7 +36,7 @@ LL | declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) error: a `const` item should never be interior mutable - --> $DIR/others.rs:48:13 + --> $DIR/others.rs:43:13 | LL | const _BAZ: Cell = Cell::new(0); //~ ERROR interior mutable | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 8454602cef2d4ce1271425c0924303955ee47e82 Mon Sep 17 00:00:00 2001 From: tabokie Date: Fri, 22 Jul 2022 13:17:10 +0800 Subject: [PATCH 30/42] Add `[assertions_on_result_states]` lint Signed-off-by: tabokie --- CHANGELOG.md | 1 + .../src/assertions_on_result_states.rs | 98 +++++++++++++++++++ clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/lib.register_style.rs | 1 + clippy_lints/src/lib.rs | 2 + tests/ui/assertions_on_result_states.fixed | 69 +++++++++++++ tests/ui/assertions_on_result_states.rs | 69 +++++++++++++ tests/ui/assertions_on_result_states.stderr | 40 ++++++++ 9 files changed, 282 insertions(+) create mode 100644 clippy_lints/src/assertions_on_result_states.rs create mode 100644 tests/ui/assertions_on_result_states.fixed create mode 100644 tests/ui/assertions_on_result_states.rs create mode 100644 tests/ui/assertions_on_result_states.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5c216c154a..e64e5d94bc95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3440,6 +3440,7 @@ Released 2018-09-13 [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions [`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore [`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants +[`assertions_on_result_states`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_result_states [`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern [`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops [`async_yields_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#async_yields_async diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs new file mode 100644 index 000000000000..b6affdee5236 --- /dev/null +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -0,0 +1,98 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn}; +use clippy_utils::path_res; +use clippy_utils::source::snippet_with_context; +use clippy_utils::ty::{implements_trait, is_copy, is_type_diagnostic_item}; +use clippy_utils::usage::local_used_after_expr; +use rustc_errors::Applicability; +use rustc_hir::def::Res; +use rustc_hir::{Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::sym; + +declare_clippy_lint! { + /// ### What it does + /// Checks for `assert!(r.is_ok())` or `assert!(r.is_err())` calls. + /// + /// ### Why is this bad? + /// An assertion failure cannot output an useful message of the error. + /// + /// ### Example + /// ```rust,ignore + /// # let r = Ok::<_, ()>(()); + /// assert!(r.is_ok()); + /// # let r = Err::<_, ()>(()); + /// assert!(r.is_err()); + /// ``` + #[clippy::version = "1.64.0"] + pub ASSERTIONS_ON_RESULT_STATES, + style, + "`assert!(r.is_ok())`/`assert!(r.is_err())` gives worse error message than directly calling `r.unwrap()`/`r.unwrap_err()`" +} + +declare_lint_pass!(AssertionsOnResultStates => [ASSERTIONS_ON_RESULT_STATES]); + +impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates { + fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) { + if let Some(macro_call) = root_macro_call_first_node(cx, e) + && matches!(cx.tcx.get_diagnostic_name(macro_call.def_id), Some(sym::assert_macro)) + && let Some((condition, panic_expn)) = find_assert_args(cx, e, macro_call.expn) + && matches!(panic_expn, PanicExpn::Empty) + && let ExprKind::MethodCall(method_segment, [recv], _) = condition.kind + && let result_type_with_refs = cx.typeck_results().expr_ty(recv) + && let result_type = result_type_with_refs.peel_refs() + && is_type_diagnostic_item(cx, result_type, sym::Result) + && let ty::Adt(_, substs) = result_type.kind() + { + if !is_copy(cx, result_type) { + if result_type_with_refs != result_type { + return; + } else if let Res::Local(binding_id) = path_res(cx, recv) + && local_used_after_expr(cx, binding_id, recv) { + return; + } + } + let mut app = Applicability::MachineApplicable; + match method_segment.ident.as_str() { + "is_ok" if has_debug_impl(cx, substs.type_at(1)) => { + span_lint_and_sugg( + cx, + ASSERTIONS_ON_RESULT_STATES, + macro_call.span, + "called `assert!` with `Result::is_ok`", + "replace with", + format!( + "{}.unwrap()", + snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 + ), + app, + ); + } + "is_err" if has_debug_impl(cx, substs.type_at(0)) => { + span_lint_and_sugg( + cx, + ASSERTIONS_ON_RESULT_STATES, + macro_call.span, + "called `assert!` with `Result::is_err`", + "replace with", + format!( + "{}.unwrap_err()", + snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0 + ), + app, + ); + } + _ => (), + }; + } + } +} + +/// This checks whether a given type is known to implement Debug. +fn has_debug_impl<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + cx.tcx + .get_diagnostic_item(sym::Debug) + .map_or(false, |debug| implements_trait(cx, ty, debug, &[])) +} diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 0ba9b7ae7e58..5be1c417bf8f 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -6,6 +6,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE), LintId::of(approx_const::APPROX_CONSTANT), LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES), LintId::of(async_yields_async::ASYNC_YIELDS_ASYNC), LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS), LintId::of(attrs::DEPRECATED_CFG_ATTR), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index d40c6aec04ae..65e01de2b160 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -42,6 +42,7 @@ store.register_lints(&[ asm_syntax::INLINE_ASM_X86_ATT_SYNTAX, asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX, assertions_on_constants::ASSERTIONS_ON_CONSTANTS, + assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES, async_yields_async::ASYNC_YIELDS_ASYNC, attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON, attrs::BLANKET_CLIPPY_RESTRICTION_LINTS, diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index e95bab1d0454..e029a5235e72 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -4,6 +4,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS), + LintId::of(assertions_on_result_states::ASSERTIONS_ON_RESULT_STATES), LintId::of(blacklisted_name::BLACKLISTED_NAME), LintId::of(blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS), LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8444532f6bbf..6cd1928e9c1c 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -174,6 +174,7 @@ mod as_conversions; mod as_underscore; mod asm_syntax; mod assertions_on_constants; +mod assertions_on_result_states; mod async_yields_async; mod attrs; mod await_holding_invalid; @@ -727,6 +728,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(unnecessary_sort_by::UnnecessarySortBy)); store.register_late_pass(move || Box::new(unnecessary_wraps::UnnecessaryWraps::new(avoid_breaking_exported_api))); store.register_late_pass(|| Box::new(assertions_on_constants::AssertionsOnConstants)); + store.register_late_pass(|| Box::new(assertions_on_result_states::AssertionsOnResultStates)); store.register_late_pass(|| Box::new(transmuting_null::TransmutingNull)); store.register_late_pass(|| Box::new(path_buf_push_overwrite::PathBufPushOverwrite)); store.register_late_pass(|| Box::new(inherent_to_string::InherentToString)); diff --git a/tests/ui/assertions_on_result_states.fixed b/tests/ui/assertions_on_result_states.fixed new file mode 100644 index 000000000000..7bde72e4b6b5 --- /dev/null +++ b/tests/ui/assertions_on_result_states.fixed @@ -0,0 +1,69 @@ +// run-rustfix +#![warn(clippy::assertions_on_result_states)] + +use std::result::Result; + +struct Foo; + +#[derive(Debug)] +struct DebugFoo; + +#[derive(Copy, Clone, Debug)] +struct CopyFoo; + +macro_rules! get_ok_macro { + () => { + Ok::<_, DebugFoo>(Foo) + }; +} + +fn main() { + // test ok + let r: Result = Ok(Foo); + debug_assert!(r.is_ok()); + r.unwrap(); + + // test ok with non-debug error type + let r: Result = Ok(Foo); + assert!(r.is_ok()); + + // test temporary ok + fn get_ok() -> Result { + Ok(Foo) + } + get_ok().unwrap(); + + // test macro ok + get_ok_macro!().unwrap(); + + // test ok that shouldn't be moved + let r: Result = Ok(CopyFoo); + fn test_ref_unmoveable_ok(r: &Result) { + assert!(r.is_ok()); + } + test_ref_unmoveable_ok(&r); + assert!(r.is_ok()); + r.unwrap(); + + // test ok that is copied + let r: Result = Ok(CopyFoo); + r.unwrap(); + r.unwrap(); + + // test reference to ok + let r: Result = Ok(CopyFoo); + fn test_ref_copy_ok(r: &Result) { + r.unwrap(); + } + test_ref_copy_ok(&r); + r.unwrap(); + + // test err + let r: Result = Err(Foo); + debug_assert!(r.is_err()); + r.unwrap_err(); + + // test err with non-debug value type + let r: Result = Err(Foo); + assert!(r.is_err()); +} diff --git a/tests/ui/assertions_on_result_states.rs b/tests/ui/assertions_on_result_states.rs new file mode 100644 index 000000000000..4c5af81efc23 --- /dev/null +++ b/tests/ui/assertions_on_result_states.rs @@ -0,0 +1,69 @@ +// run-rustfix +#![warn(clippy::assertions_on_result_states)] + +use std::result::Result; + +struct Foo; + +#[derive(Debug)] +struct DebugFoo; + +#[derive(Copy, Clone, Debug)] +struct CopyFoo; + +macro_rules! get_ok_macro { + () => { + Ok::<_, DebugFoo>(Foo) + }; +} + +fn main() { + // test ok + let r: Result = Ok(Foo); + debug_assert!(r.is_ok()); + assert!(r.is_ok()); + + // test ok with non-debug error type + let r: Result = Ok(Foo); + assert!(r.is_ok()); + + // test temporary ok + fn get_ok() -> Result { + Ok(Foo) + } + assert!(get_ok().is_ok()); + + // test macro ok + assert!(get_ok_macro!().is_ok()); + + // test ok that shouldn't be moved + let r: Result = Ok(CopyFoo); + fn test_ref_unmoveable_ok(r: &Result) { + assert!(r.is_ok()); + } + test_ref_unmoveable_ok(&r); + assert!(r.is_ok()); + r.unwrap(); + + // test ok that is copied + let r: Result = Ok(CopyFoo); + assert!(r.is_ok()); + r.unwrap(); + + // test reference to ok + let r: Result = Ok(CopyFoo); + fn test_ref_copy_ok(r: &Result) { + assert!(r.is_ok()); + } + test_ref_copy_ok(&r); + r.unwrap(); + + // test err + let r: Result = Err(Foo); + debug_assert!(r.is_err()); + assert!(r.is_err()); + + // test err with non-debug value type + let r: Result = Err(Foo); + assert!(r.is_err()); +} diff --git a/tests/ui/assertions_on_result_states.stderr b/tests/ui/assertions_on_result_states.stderr new file mode 100644 index 000000000000..13c2dd877a97 --- /dev/null +++ b/tests/ui/assertions_on_result_states.stderr @@ -0,0 +1,40 @@ +error: called `assert!` with `Result::is_ok` + --> $DIR/assertions_on_result_states.rs:24:5 + | +LL | assert!(r.is_ok()); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()` + | + = note: `-D clippy::assertions-on-result-states` implied by `-D warnings` + +error: called `assert!` with `Result::is_ok` + --> $DIR/assertions_on_result_states.rs:34:5 + | +LL | assert!(get_ok().is_ok()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok().unwrap()` + +error: called `assert!` with `Result::is_ok` + --> $DIR/assertions_on_result_states.rs:37:5 + | +LL | assert!(get_ok_macro!().is_ok()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `get_ok_macro!().unwrap()` + +error: called `assert!` with `Result::is_ok` + --> $DIR/assertions_on_result_states.rs:50:5 + | +LL | assert!(r.is_ok()); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()` + +error: called `assert!` with `Result::is_ok` + --> $DIR/assertions_on_result_states.rs:56:9 + | +LL | assert!(r.is_ok()); + | ^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap()` + +error: called `assert!` with `Result::is_err` + --> $DIR/assertions_on_result_states.rs:64:5 + | +LL | assert!(r.is_err()); + | ^^^^^^^^^^^^^^^^^^^ help: replace with: `r.unwrap_err()` + +error: aborting due to 6 previous errors + From 419d39c072825bb622e262c59b0243fcfe69d337 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 6 Jun 2022 20:26:41 +0200 Subject: [PATCH 31/42] Clippy fallout. --- clippy_lints/src/lifetimes.rs | 78 +++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 083c437a293c..826353aafc06 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -9,12 +9,14 @@ use rustc_hir::intravisit::{ use rustc_hir::FnRetTy::Return; use rustc_hir::{ BareFnTy, BodyId, FnDecl, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, ImplItem, - ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, LifetimeParamKind, ParamName, PolyTraitRef, - PredicateOrigin, TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, + ImplItemKind, Item, ItemKind, LangItem, Lifetime, LifetimeName, ParamName, PolyTraitRef, PredicateOrigin, + TraitBoundModifier, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter as middle_nested_filter; +use rustc_middle::ty::TyCtxt; use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::def_id::LocalDefId; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, Ident, Symbol}; @@ -129,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes { enum RefLt { Unnamed, Static, - Named(Symbol), + Named(LocalDefId), } fn check_fn_inner<'tcx>( @@ -232,7 +234,7 @@ fn could_use_elision<'tcx>( // level of the current item. // check named LTs - let allowed_lts = allowed_lts_from(named_generics); + let allowed_lts = allowed_lts_from(cx.tcx, named_generics); // these will collect all the lifetimes for references in arg/return types let mut input_visitor = RefVisitor::new(cx); @@ -254,22 +256,6 @@ fn could_use_elision<'tcx>( return false; } - if allowed_lts - .intersection( - &input_visitor - .nested_elision_site_lts - .iter() - .chain(output_visitor.nested_elision_site_lts.iter()) - .cloned() - .filter(|v| matches!(v, RefLt::Named(_))) - .collect(), - ) - .next() - .is_some() - { - return false; - } - let input_lts = input_visitor.lts; let output_lts = output_visitor.lts; @@ -303,6 +289,31 @@ fn could_use_elision<'tcx>( } } + // check for higher-ranked trait bounds + if !input_visitor.nested_elision_site_lts.is_empty() || !output_visitor.nested_elision_site_lts.is_empty() { + let allowed_lts: FxHashSet<_> = allowed_lts + .iter() + .filter_map(|lt| match lt { + RefLt::Named(def_id) => Some(cx.tcx.item_name(def_id.to_def_id())), + _ => None, + }) + .collect(); + for lt in input_visitor.nested_elision_site_lts { + if let RefLt::Named(def_id) = lt { + if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) { + return false; + } + } + } + for lt in output_visitor.nested_elision_site_lts { + if let RefLt::Named(def_id) = lt { + if allowed_lts.contains(&cx.tcx.item_name(def_id.to_def_id())) { + return false; + } + } + } + } + // no input lifetimes? easy case! if input_lts.is_empty() { false @@ -335,14 +346,11 @@ fn could_use_elision<'tcx>( } } -fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet { +fn allowed_lts_from(tcx: TyCtxt<'_>, named_generics: &[GenericParam<'_>]) -> FxHashSet { let mut allowed_lts = FxHashSet::default(); for par in named_generics.iter() { - if let GenericParamKind::Lifetime { - kind: LifetimeParamKind::Explicit, - } = par.kind - { - allowed_lts.insert(RefLt::Named(par.name.ident().name)); + if let GenericParamKind::Lifetime { .. } = par.kind { + allowed_lts.insert(RefLt::Named(tcx.hir().local_def_id(par.hir_id))); } } allowed_lts.insert(RefLt::Unnamed); @@ -385,8 +393,10 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> { self.lts.push(RefLt::Unnamed); } else if lt.is_elided() { self.lts.push(RefLt::Unnamed); + } else if let LifetimeName::Param(def_id, _) = lt.name { + self.lts.push(RefLt::Named(def_id)); } else { - self.lts.push(RefLt::Named(lt.name.ident().name)); + self.lts.push(RefLt::Unnamed); } } else { self.lts.push(RefLt::Unnamed); @@ -434,10 +444,15 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { TyKind::OpaqueDef(item, bounds) => { let map = self.cx.tcx.hir(); let item = map.item(item); + let len = self.lts.len(); walk_item(self, item); - walk_ty(self, ty); + self.lts.truncate(len); self.lts.extend(bounds.iter().filter_map(|bound| match bound { - GenericArg::Lifetime(l) => Some(RefLt::Named(l.name.ident().name)), + GenericArg::Lifetime(l) => Some(if let LifetimeName::Param(def_id, _) = l.name { + RefLt::Named(def_id) + } else { + RefLt::Unnamed + }), _ => None, })); }, @@ -456,9 +471,8 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { } return; }, - _ => (), + _ => walk_ty(self, ty), } - walk_ty(self, ty); } } @@ -477,7 +491,7 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ return true; } // if the bounds define new lifetimes, they are fine to occur - let allowed_lts = allowed_lts_from(pred.bound_generic_params); + let allowed_lts = allowed_lts_from(cx.tcx, pred.bound_generic_params); // now walk the bounds for bound in pred.bounds.iter() { walk_param_bound(&mut visitor, bound); From 51cd5a866728539c1d1db81c437c474ffbef7ccf Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:58:38 -0400 Subject: [PATCH 32/42] Add `--type` flag to `dev new_lint` --- clippy_dev/src/main.rs | 9 +- clippy_dev/src/new_lint.rs | 294 ++++++++++++++++++++++++++---- clippy_dev/src/update_lints.rs | 10 +- clippy_lints/src/cargo/mod.rs | 10 +- clippy_lints/src/matches/mod.rs | 20 +- clippy_lints/src/operators/mod.rs | 12 +- 6 files changed, 295 insertions(+), 60 deletions(-) diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index a29ba2d0c85e..2008942d0879 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -37,6 +37,7 @@ fn main() { matches.get_one::("pass"), matches.get_one::("name"), matches.get_one::("category"), + matches.get_one::("type"), matches.contains_id("msrv"), ) { Ok(_) => update_lints::update(update_lints::UpdateMode::Change), @@ -157,7 +158,8 @@ fn get_clap_config() -> ArgMatches { .help("Specify whether the lint runs during the early or late pass") .takes_value(true) .value_parser([PossibleValue::new("early"), PossibleValue::new("late")]) - .required(true), + .conflicts_with("type") + .required_unless_present("type"), Arg::new("name") .short('n') .long("name") @@ -183,6 +185,11 @@ fn get_clap_config() -> ArgMatches { PossibleValue::new("internal_warn"), ]) .takes_value(true), + Arg::new("type") + .long("type") + .help("What directory the lint belongs in") + .takes_value(true) + .required(false), Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint"), ]), Command::new("setup") diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 7d7e760ef446..af22cb899421 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -1,5 +1,5 @@ use crate::clippy_project_root; -use indoc::indoc; +use indoc::{indoc, writedoc}; use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; @@ -10,6 +10,7 @@ struct LintData<'a> { pass: &'a str, name: &'a str, category: &'a str, + ty: Option<&'a str>, project_root: PathBuf, } @@ -38,25 +39,35 @@ pub fn create( pass: Option<&String>, lint_name: Option<&String>, category: Option<&String>, + ty: Option<&String>, msrv: bool, ) -> io::Result<()> { let lint = LintData { - pass: pass.expect("`pass` argument is validated by clap"), + pass: pass.map_or("", String::as_str), name: lint_name.expect("`name` argument is validated by clap"), category: category.expect("`category` argument is validated by clap"), + ty: ty.map(String::as_str), project_root: clippy_project_root(), }; create_lint(&lint, msrv).context("Unable to create lint implementation")?; create_test(&lint).context("Unable to create a test for the new lint")?; - add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs") + + if lint.ty.is_none() { + add_lint(&lint, msrv).context("Unable to add lint to clippy_lints/src/lib.rs")?; + } + + Ok(()) } fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { - let lint_contents = get_lint_file_contents(lint, enable_msrv); - - let lint_path = format!("clippy_lints/src/{}.rs", lint.name); - write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes()) + if let Some(ty) = lint.ty { + generate_from_ty(lint, enable_msrv, ty) + } else { + let lint_contents = get_lint_file_contents(lint, enable_msrv); + let lint_path = format!("clippy_lints/src/{}.rs", lint.name); + write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes()) + } } fn create_test(lint: &LintData<'_>) -> io::Result<()> { @@ -204,7 +215,6 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { }, }; - let version = get_stabilization_version(); let lint_name = lint.name; let category = lint.category; let name_camel = to_camel_case(lint.name); @@ -238,32 +248,7 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { ) }); - let _ = write!( - result, - indoc! {r#" - declare_clippy_lint! {{ - /// ### What it does - /// - /// ### Why is this bad? - /// - /// ### Example - /// ```rust - /// // example code where clippy issues a warning - /// ``` - /// Use instead: - /// ```rust - /// // example code which does not raise clippy warning - /// ``` - #[clippy::version = "{version}"] - pub {name_upper}, - {category}, - "default lint description" - }} - "#}, - version = version, - name_upper = name_upper, - category = category, - ); + let _ = write!(result, "{}", get_lint_declaration(&name_upper, category)); result.push_str(&if enable_msrv { format!( @@ -312,6 +297,247 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { result } +fn get_lint_declaration(name_upper: &str, category: &str) -> String { + format!( + indoc! {r#" + declare_clippy_lint! {{ + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// // example code where clippy issues a warning + /// ``` + /// Use instead: + /// ```rust + /// // example code which does not raise clippy warning + /// ``` + #[clippy::version = "{version}"] + pub {name_upper}, + {category}, + "default lint description" + }} + "#}, + version = get_stabilization_version(), + name_upper = name_upper, + category = category, + ) +} + +fn generate_from_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::Result<()> { + if ty == "cargo" { + assert_eq!( + lint.category, "cargo", + "Lints of type `cargo` must have the `cargo` category" + ); + } + + let ty_dir = lint.project_root.join(format!("clippy_lints/src/{}", ty)); + assert!( + ty_dir.exists() && ty_dir.is_dir(), + "Directory `{}` does not exist!", + ty_dir.display() + ); + + let lint_file_path = ty_dir.join(format!("{}.rs", lint.name)); + assert!( + !lint_file_path.exists(), + "File `{}` already exists", + lint_file_path.display() + ); + + let mod_file_path = ty_dir.join("mod.rs"); + let context_import = setup_mod_file(&mod_file_path, lint)?; + + let name_upper = lint.name.to_uppercase(); + let mut lint_file_contents = String::new(); + + if enable_msrv { + let _ = writedoc!( + lint_file_contents, + r#" + use clippy_utils::{{meets_msrv, msrvs}}; + use rustc_lint::{{{context_import}, LintContext}}; + use rustc_semver::RustcVersion; + + use super::{name_upper}; + + // TODO: Adjust the parameters as necessary + pub(super) fn check(cx: &{context_import}, msrv: Option) {{ + if !meets_msrv(msrv, todo!("Add a new entry in `clippy_utils/src/msrvs`")) {{ + return; + }} + todo!(); + }} + "#, + context_import = context_import, + name_upper = name_upper, + ); + } else { + let _ = writedoc!( + lint_file_contents, + r#" + use rustc_lint::{{{context_import}, LintContext}}; + + use super::{name_upper}; + + // TODO: Adjust the parameters as necessary + pub(super) fn check(cx: &{context_import}) {{ + todo!(); + }} + "#, + context_import = context_import, + name_upper = name_upper, + ); + } + + write_file(lint_file_path, lint_file_contents)?; + + Ok(()) +} + +#[allow(clippy::too_many_lines)] +fn setup_mod_file(path: &Path, lint: &LintData<'_>) -> io::Result<&'static str> { + use super::update_lints::{match_tokens, LintDeclSearchResult}; + use rustc_lexer::TokenKind; + + let lint_name_upper = lint.name.to_uppercase(); + + let mut file_contents = fs::read_to_string(path)?; + assert!( + !file_contents.contains(&lint_name_upper), + "Lint `{}` already defined in `{}`", + lint.name, + path.display() + ); + + let mut offset = 0usize; + let mut last_decl_curly_offset = None; + let mut lint_context = None; + + let mut iter = rustc_lexer::tokenize(&file_contents).map(|t| { + let range = offset..offset + t.len; + offset = range.end; + + LintDeclSearchResult { + token_kind: t.kind, + content: &file_contents[range.clone()], + range, + } + }); + + // Find both the last lint declaration (declare_clippy_lint!) and the lint pass impl + while let Some(LintDeclSearchResult { content, .. }) = iter.find(|result| result.token_kind == TokenKind::Ident) { + let mut iter = iter + .by_ref() + .filter(|t| !matches!(t.token_kind, TokenKind::Whitespace | TokenKind::LineComment { .. })); + + match content { + "declare_clippy_lint" => { + // matches `!{` + match_tokens!(iter, Bang OpenBrace); + if let Some(LintDeclSearchResult { range, .. }) = + iter.find(|result| result.token_kind == TokenKind::CloseBrace) + { + last_decl_curly_offset = Some(range.end); + } + }, + "impl" => { + let mut token = iter.next(); + match token { + // matches <'foo> + Some(LintDeclSearchResult { + token_kind: TokenKind::Lt, + .. + }) => { + match_tokens!(iter, Lifetime { .. } Gt); + token = iter.next(); + }, + None => break, + _ => {}, + } + + if let Some(LintDeclSearchResult { + token_kind: TokenKind::Ident, + content, + .. + }) = token + { + // Get the appropriate lint context struct + lint_context = match content { + "LateLintPass" => Some("LateContext"), + "EarlyLintPass" => Some("EarlyContext"), + _ => continue, + }; + } + }, + _ => {}, + } + } + + drop(iter); + + let last_decl_curly_offset = + last_decl_curly_offset.unwrap_or_else(|| panic!("No lint declarations found in `{}`", path.display())); + let lint_context = + lint_context.unwrap_or_else(|| panic!("No lint pass implementation found in `{}`", path.display())); + + // Add the lint declaration to `mod.rs` + file_contents.replace_range( + // Remove the trailing newline, which should always be present + last_decl_curly_offset..=last_decl_curly_offset, + &format!("\n\n{}", get_lint_declaration(&lint_name_upper, lint.category)), + ); + + // Add the lint to `impl_lint_pass`/`declare_lint_pass` + let impl_lint_pass_start = file_contents.find("impl_lint_pass!").unwrap_or_else(|| { + file_contents + .find("declare_lint_pass!") + .unwrap_or_else(|| panic!("failed to find `impl_lint_pass`/`declare_lint_pass`")) + }); + + let mut arr_start = file_contents[impl_lint_pass_start..].find('[').unwrap_or_else(|| { + panic!("malformed `impl_lint_pass`/`declare_lint_pass`"); + }); + + arr_start += impl_lint_pass_start; + + let mut arr_end = file_contents[arr_start..] + .find(']') + .expect("failed to find `impl_lint_pass` terminator"); + + arr_end += arr_start; + + let mut arr_content = file_contents[arr_start + 1..arr_end].to_string(); + arr_content.retain(|c| !c.is_whitespace()); + + let mut new_arr_content = String::new(); + for ident in arr_content + .split(',') + .chain(std::iter::once(&*lint_name_upper)) + .filter(|s| !s.is_empty()) + { + let _ = write!(new_arr_content, "\n {},", ident); + } + new_arr_content.push('\n'); + + file_contents.replace_range(arr_start + 1..arr_end, &new_arr_content); + + // Just add the mod declaration at the top, it'll be fixed by rustfmt + file_contents.insert_str(0, &format!("mod {};\n", &lint.name)); + + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(path) + .context(format!("trying to open: `{}`", path.display()))?; + file.write_all(file_contents.as_bytes()) + .context(format!("writing to file: `{}`", path.display()))?; + + Ok(lint_context) +} + #[test] fn test_camel_case() { let s = "a_lint"; diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index c089f4d8ce4b..aed38bc28176 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -824,10 +824,12 @@ macro_rules! match_tokens { } } -struct LintDeclSearchResult<'a> { - token_kind: TokenKind, - content: &'a str, - range: Range, +pub(crate) use match_tokens; + +pub(crate) struct LintDeclSearchResult<'a> { + pub token_kind: TokenKind, + pub content: &'a str, + pub range: Range, } /// Parse a source file looking for `declare_clippy_lint` macro invocations. diff --git a/clippy_lints/src/cargo/mod.rs b/clippy_lints/src/cargo/mod.rs index abe95c6663f7..9f45db86a091 100644 --- a/clippy_lints/src/cargo/mod.rs +++ b/clippy_lints/src/cargo/mod.rs @@ -1,3 +1,8 @@ +mod common_metadata; +mod feature_name; +mod multiple_crate_versions; +mod wildcard_dependencies; + use cargo_metadata::MetadataCommand; use clippy_utils::diagnostics::span_lint; use clippy_utils::is_lint_allowed; @@ -6,11 +11,6 @@ use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::DUMMY_SP; -mod common_metadata; -mod feature_name; -mod multiple_crate_versions; -mod wildcard_dependencies; - declare_clippy_lint! { /// ### What it does /// Checks to see if all common metadata is defined in diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index a3d69a4d09c8..b638f2716028 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1,13 +1,3 @@ -use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context}; -use clippy_utils::{higher, in_constant, meets_msrv, msrvs}; -use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat}; -use rustc_lexer::{tokenize, TokenKind}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_semver::RustcVersion; -use rustc_session::{declare_tool_lint, impl_lint_pass}; -use rustc_span::{Span, SpanData, SyntaxContext}; - mod collapsible_match; mod infallible_destructuring_match; mod manual_map; @@ -31,6 +21,16 @@ mod single_match; mod try_err; mod wild_in_or_pats; +use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context}; +use clippy_utils::{higher, in_constant, meets_msrv, msrvs}; +use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat}; +use rustc_lexer::{tokenize, TokenKind}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_semver::RustcVersion; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{Span, SpanData, SyntaxContext}; + declare_clippy_lint! { /// ### What it does /// Checks for matches with a single arm where an `if let` diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index c688d94bb520..bb6d99406b49 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -1,9 +1,3 @@ -use rustc_hir::{Body, Expr, ExprKind, UnOp}; -use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; - -pub(crate) mod arithmetic; - mod absurd_extreme_comparisons; mod assign_op_pattern; mod bit_mask; @@ -27,6 +21,12 @@ mod ptr_eq; mod self_assignment; mod verbose_bit_mask; +pub(crate) mod arithmetic; + +use rustc_hir::{Body, Expr, ExprKind, UnOp}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_tool_lint, impl_lint_pass}; + declare_clippy_lint! { /// ### What it does /// Checks for comparisons where one side of the relation is From 034b6f99ad982927e27565384af16647385520a6 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 13 Jun 2022 08:22:06 +0200 Subject: [PATCH 33/42] Replace LifetimeRes::Anonymous by LifetimeRes::Infer. --- clippy_lints/src/manual_async_fn.rs | 2 +- clippy_lints/src/ptr.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index 945880d21471..a0ca7e6ff1e2 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -166,7 +166,7 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) // - There's only one output lifetime bound using `+ '_` // - All input lifetimes are explicitly bound to the output input_lifetimes.is_empty() - || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Underscore)) + || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Infer)) || input_lifetimes .iter() .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt)) diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 8534d8a29f10..3c5ea2d94144 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -351,7 +351,7 @@ impl fmt::Display for RefPrefix { name.fmt(f)?; f.write_char(' ')?; }, - LifetimeName::Underscore => f.write_str("'_ ")?, + LifetimeName::Infer => f.write_str("'_ ")?, LifetimeName::Static => f.write_str("'static ")?, _ => (), } From 8edcf6cd1e432bc3119f88d6ad782f8c473f134b Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 13 Jun 2022 11:53:31 +0200 Subject: [PATCH 34/42] Clippy fallout. --- clippy_lints/src/lifetimes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 826353aafc06..3f69cc203883 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -615,7 +615,7 @@ struct BodyLifetimeChecker { impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker { // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { - if lifetime.name.ident().name != kw::Empty && lifetime.name.ident().name != kw::StaticLifetime { + if lifetime.name.ident().name != kw::UnderscoreLifetime && lifetime.name.ident().name != kw::StaticLifetime { self.lifetimes_used_in_body = true; } } From c8ee8c30f00214fcc3849d3124501e5e9768bb53 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 26 Jul 2022 19:18:10 -0400 Subject: [PATCH 35/42] Give the user more information during creation --- clippy_dev/src/new_lint.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index af22cb899421..4eea25b66ded 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -62,11 +62,14 @@ pub fn create( fn create_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { if let Some(ty) = lint.ty { - generate_from_ty(lint, enable_msrv, ty) + create_lint_for_ty(lint, enable_msrv, ty) } else { let lint_contents = get_lint_file_contents(lint, enable_msrv); let lint_path = format!("clippy_lints/src/{}.rs", lint.name); - write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes()) + write_file(lint.project_root.join(&lint_path), lint_contents.as_bytes())?; + println!("Generated lint file: `{}`", lint_path); + + Ok(()) } } @@ -86,16 +89,22 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { if lint.category == "cargo" { let relative_test_dir = format!("tests/ui-cargo/{}", lint.name); - let test_dir = lint.project_root.join(relative_test_dir); + let test_dir = lint.project_root.join(&relative_test_dir); fs::create_dir(&test_dir)?; create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?; - create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint") + create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")?; + + println!("Generated test directories: `{}`, `{}`", format!("{}/pass", relative_test_dir), format!("{}/fail", relative_test_dir)); } else { let test_path = format!("tests/ui/{}.rs", lint.name); let test_contents = get_test_file_contents(lint.name, None); - write_file(lint.project_root.join(test_path), test_contents) + write_file(lint.project_root.join(&test_path), test_contents)?; + + println!("Generated test file: `{}`", test_path); } + + Ok(()) } fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { @@ -325,12 +334,14 @@ fn get_lint_declaration(name_upper: &str, category: &str) -> String { ) } -fn generate_from_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::Result<()> { - if ty == "cargo" { - assert_eq!( +fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::Result<()> { + match ty { + "cargo" => assert_eq!( lint.category, "cargo", "Lints of type `cargo` must have the `cargo` category" - ); + ), + _ if lint.category == "cargo" => panic!("Lints of category `cargo` must have the `cargo` type"), + _ => {} } let ty_dir = lint.project_root.join(format!("clippy_lints/src/{}", ty)); @@ -392,7 +403,9 @@ fn generate_from_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::Res ); } - write_file(lint_file_path, lint_file_contents)?; + write_file(lint_file_path.as_path(), lint_file_contents)?; + println!("Generated lint file: `clippy_lints/src/{}/{}.rs`", ty, lint.name); + println!("Be sure to add a call to `{}::check` in `clippy_lints/src/{}/mod.rs`!", lint.name, ty); Ok(()) } From f31937043dbd8a10309ad742cd9e1a49721a09e5 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 26 Jul 2022 19:32:34 -0400 Subject: [PATCH 36/42] Implicitly set `--type=cargo` when using `--category=cargo` --- clippy_dev/src/main.rs | 4 ++-- clippy_dev/src/new_lint.rs | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index 2008942d0879..a417d3dd8a4e 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -36,8 +36,8 @@ fn main() { match new_lint::create( matches.get_one::("pass"), matches.get_one::("name"), - matches.get_one::("category"), - matches.get_one::("type"), + matches.get_one::("category").map(String::as_str), + matches.get_one::("type").map(String::as_str), matches.contains_id("msrv"), ) { Ok(_) => update_lints::update(update_lints::UpdateMode::Change), diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 4eea25b66ded..03d2ef3d19ed 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -38,15 +38,20 @@ impl Context for io::Result { pub fn create( pass: Option<&String>, lint_name: Option<&String>, - category: Option<&String>, - ty: Option<&String>, + category: Option<&str>, + mut ty: Option<&str>, msrv: bool, ) -> io::Result<()> { + if category == Some("cargo") && ty.is_none() { + // `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo` + ty = Some("cargo"); + } + let lint = LintData { pass: pass.map_or("", String::as_str), name: lint_name.expect("`name` argument is validated by clap"), category: category.expect("`category` argument is validated by clap"), - ty: ty.map(String::as_str), + ty, project_root: clippy_project_root(), }; @@ -95,7 +100,7 @@ fn create_test(lint: &LintData<'_>) -> io::Result<()> { create_project_layout(lint.name, &test_dir, "fail", "Content that triggers the lint goes here")?; create_project_layout(lint.name, &test_dir, "pass", "This file should not trigger the lint")?; - println!("Generated test directories: `{}`, `{}`", format!("{}/pass", relative_test_dir), format!("{}/fail", relative_test_dir)); + println!("Generated test directories: `{relative_test_dir}/pass`, `{relative_test_dir}/fail`"); } else { let test_path = format!("tests/ui/{}.rs", lint.name); let test_contents = get_test_file_contents(lint.name, None); @@ -341,7 +346,7 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R "Lints of type `cargo` must have the `cargo` category" ), _ if lint.category == "cargo" => panic!("Lints of category `cargo` must have the `cargo` type"), - _ => {} + _ => {}, } let ty_dir = lint.project_root.join(format!("clippy_lints/src/{}", ty)); @@ -405,7 +410,10 @@ fn create_lint_for_ty(lint: &LintData<'_>, enable_msrv: bool, ty: &str) -> io::R write_file(lint_file_path.as_path(), lint_file_contents)?; println!("Generated lint file: `clippy_lints/src/{}/{}.rs`", ty, lint.name); - println!("Be sure to add a call to `{}::check` in `clippy_lints/src/{}/mod.rs`!", lint.name, ty); + println!( + "Be sure to add a call to `{}::check` in `clippy_lints/src/{}/mod.rs`!", + lint.name, ty + ); Ok(()) } From 9164e3aa5226a39579f141433032c15c4b9dc310 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Wed, 27 Jul 2022 16:49:34 +0900 Subject: [PATCH 37/42] Use `action/checkout@v3` in the book Signed-off-by: Yuki Okushi --- book/src/continuous_integration/github_actions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md index 5c8280f17c83..339287a7dd95 100644 --- a/book/src/continuous_integration/github_actions.md +++ b/book/src/continuous_integration/github_actions.md @@ -15,7 +15,7 @@ jobs: clippy_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Run Clippy run: cargo clippy --all-targets --all-features ``` From 257259118c0df008b6cf939586193fa0d2a3024e Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 25 Jul 2022 13:02:39 +0100 Subject: [PATCH 38/42] lint: add bad opt access internal lint Some command-line options accessible through `sess.opts` are best accessed through wrapper functions on `Session`, `TyCtxt` or otherwise, rather than through field access on the option struct in the `Session`. Adds a new lint which triggers on those options that should be accessed through a wrapper function so that this is prohibited. Options are annotated with a new attribute `rustc_lint_opt_deny_field_access` which can specify the error message (i.e. "use this other function instead") to be emitted. A simpler alternative would be to simply rename the options in the option type so that it is clear they should not be used, however this doesn't prevent uses, just discourages them. Another alternative would be to make the option fields private, and adding accessor functions on the option types, however the wrapper functions sometimes rely on additional state from `Session` or `TyCtxt` which wouldn't be available in an function on the option type, so the accessor would simply make the field available and its use would be discouraged too. Signed-off-by: David Wood --- src/driver.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/driver.rs b/src/driver.rs index c219c7de830e..c1ec2bd5bd66 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -94,6 +94,8 @@ struct ClippyCallbacks { } impl rustc_driver::Callbacks for ClippyCallbacks { + // JUSTIFICATION: necessary in clippy driver to set `mir_opt_level` + #[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] fn config(&mut self, config: &mut interface::Config) { let previous = config.register_lints.take(); let clippy_args_var = self.clippy_args_var.take(); From 83191964990e9ea62200257b81902281c03dd4e7 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Tue, 26 Jul 2022 20:23:50 -0400 Subject: [PATCH 39/42] Document `--type` flag --- book/src/development/adding_lints.md | 45 +++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index d06297f2e079..da781eb970df 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -10,6 +10,10 @@ because that's clearly a non-descriptive name. - [Adding a new lint](#adding-a-new-lint) - [Setup](#setup) - [Getting Started](#getting-started) + - [Defining Our Lint](#defining-our-lint) + - [Standalone](#standalone) + - [Specific Type](#specific-type) + - [Tests Location](#tests-location) - [Testing](#testing) - [Cargo lints](#cargo-lints) - [Rustfix tests](#rustfix-tests) @@ -36,17 +40,38 @@ See the [Basics](basics.md#get-the-code) documentation. ## Getting Started There is a bit of boilerplate code that needs to be set up when creating a new -lint. Fortunately, you can use the clippy dev tools to handle this for you. We +lint. Fortunately, you can use the Clippy dev tools to handle this for you. We are naming our new lint `foo_functions` (lints are generally written in snake -case), and we don't need type information so it will have an early pass type -(more on this later on). If you're not sure if the name you chose fits the lint, -take a look at our [lint naming guidelines][lint_naming]. To get started on this -lint you can run `cargo dev new_lint --name=foo_functions --pass=early ---category=pedantic` (category will default to nursery if not provided). This -command will create two files: `tests/ui/foo_functions.rs` and -`clippy_lints/src/foo_functions.rs`, as well as [registering the -lint](#lint-registration). For cargo lints, two project hierarchies (fail/pass) -will be created by default under `tests/ui-cargo`. +case), and we don't need type information, so it will have an early pass type +(more on this later). If you're unsure if the name you chose fits the lint, +take a look at our [lint naming guidelines][lint_naming]. + +## Defining Our Lint +To get started, there are two ways to define our lint. + +### Standalone +Command: `cargo dev new_lint --name=foo_functions --pass=early --category=pedantic` +(category will default to nursery if not provided) + +This command will create a new file: `clippy_lints/src/foo_functions.rs`, as well +as [register the lint](#lint-registration). + +### Specific Type +Command: `cargo dev new_lint --name=foo_functions --type=functions --category=pedantic` + +This command will create a new file: `clippy_lints/src/{type}/foo_functions.rs`. + +Notice how this command has a `--type` flag instead of `--pass`. Unlike a standalone +definition, this lint won't be registered in the traditional sense. Instead, you will +call your lint from within the type's lint pass, found in `clippy_lints/src/{type}/mod.rs`. + +A "type" is just the name of a directory in `clippy_lints/src`, like `functions` in +the example command. These are groupings of lints with common behaviors, so if your +lint falls into one, it would be best to add it to that type. + +### Tests Location +Both commands will create a file: `tests/ui/foo_functions.rs`. For cargo lints, +two project hierarchies (fail/pass) will be created by default under `tests/ui-cargo`. Next, we'll open up these files and add our lint! From 1ed7bff32c3a4a62a644aed139dbb953c0725911 Mon Sep 17 00:00:00 2001 From: Serial <69764315+Serial-ATA@users.noreply.github.com> Date: Thu, 28 Jul 2022 03:40:21 -0400 Subject: [PATCH 40/42] Tell the user how to revert `dev setup intellij` --- clippy_dev/src/fmt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_dev/src/fmt.rs b/clippy_dev/src/fmt.rs index d513a229b7e3..3b27f061eb0b 100644 --- a/clippy_dev/src/fmt.rs +++ b/clippy_dev/src/fmt.rs @@ -13,7 +13,7 @@ pub enum CliError { IoError(io::Error), RustfmtNotInstalled, WalkDirError(walkdir::Error), - RaSetupActive, + IntellijSetupActive, } impl From for CliError { @@ -48,7 +48,7 @@ pub fn run(check: bool, verbose: bool) { .expect("Failed to read clippy Cargo.toml") .contains(&"[target.'cfg(NOT_A_PLATFORM)'.dependencies]") { - return Err(CliError::RaSetupActive); + return Err(CliError::IntellijSetupActive); } rustfmt_test(context)?; @@ -93,11 +93,11 @@ pub fn run(check: bool, verbose: bool) { CliError::WalkDirError(err) => { eprintln!("error: {}", err); }, - CliError::RaSetupActive => { + CliError::IntellijSetupActive => { eprintln!( "error: a local rustc repo is enabled as path dependency via `cargo dev setup intellij`. Not formatting because that would format the local repo as well! -Please revert the changes to Cargo.tomls first." +Please revert the changes to Cargo.tomls with `cargo dev remove intellij`." ); }, } From 510effce597f26abe4e4c1464fdf97c7df6bd9c8 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 28 Jul 2022 18:56:16 +0200 Subject: [PATCH 41/42] Bump nightly version -> 2022-07-28 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index e693e6837592..23ba7c712779 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-07-15" +channel = "nightly-2022-07-28" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] From d673219ea3509efc0659dbdc3496226d600e7378 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 28 Jul 2022 19:08:41 +0200 Subject: [PATCH 42/42] Update Cargo.lock --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 2325d0f3bf26..58c3982de233 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,6 +689,7 @@ dependencies = [ "tester", "tokio", "toml", + "walkdir", ] [[package]]